naive-framework 0.0.0-stage → 0.2.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 (549) hide show
  1. package/AGENTS.md +62 -0
  2. package/LICENSE +21 -0
  3. package/README.md +37 -2
  4. package/docs/decisions/decisions.json +888 -0
  5. package/docs/ontology.json +796 -0
  6. package/docs/ontology.md +163 -0
  7. package/docs/reference/ROUTES.md +124 -0
  8. package/evidence/benchmarks/2026-10-27-golden.jev.after-catalog-fix.json +558 -0
  9. package/evidence/benchmarks/2026-10-27-golden.jev.json +501 -0
  10. package/evidence/benchmarks/2026-10-27-golden.reasoning-answers.json +177 -0
  11. package/evidence/benchmarks/JEV_VS_REASONING_MODEL_2026-10-27.md +68 -0
  12. package/evidence/capabilities/agent-capabilities.json +86 -0
  13. package/evidence/design-documents.registry.json +2896 -0
  14. package/evidence/evals/evals.json +25 -0
  15. package/evidence/evals/harness-evals.json +140 -0
  16. package/evidence/layer.json +51 -0
  17. package/evidence/skills/agent-capabilities/SKILL.md +35 -0
  18. package/evidence/skills/design-coverage/SKILL.md +57 -0
  19. package/evidence/skills/design-coverage/check.mjs +192 -0
  20. package/evidence/skills/design-coverage/scan.mjs +45 -0
  21. package/evidence/skills/fidelity-ladder-audit/SKILL.md +121 -0
  22. package/evidence/skills/fidelity-ladder-audit/ladder-census.mjs +180 -0
  23. package/evidence/skills/findability/SKILL.md +32 -0
  24. package/evidence/skills/findability/inventory.rb +41 -0
  25. package/evidence/skills/foreshadowing-audit/SKILL.md +183 -0
  26. package/evidence/skills/metrics-review/SKILL.md +177 -0
  27. package/evidence/tooling/integrations.json +153 -0
  28. package/governance/layer.json +102 -0
  29. package/governance/references/references.json +26 -0
  30. package/governance/skills/board-design/SKILL.md +68 -0
  31. package/governance/skills/brand-pillar/SKILL.md +90 -0
  32. package/governance/skills/business-pillar/SKILL.md +96 -0
  33. package/governance/skills/chief-of-staff/SKILL.md +45 -0
  34. package/governance/skills/cofounder-structure/SKILL.md +64 -0
  35. package/governance/skills/cross-pillar-tradeoff/SKILL.md +168 -0
  36. package/governance/skills/equity-and-share-classes/SKILL.md +68 -0
  37. package/governance/skills/executive-roles/SKILL.md +95 -0
  38. package/governance/skills/exit-planning/SKILL.md +66 -0
  39. package/governance/skills/financing-and-investor-rights/SKILL.md +73 -0
  40. package/governance/skills/office-hours-mode/SKILL.md +113 -0
  41. package/governance/skills/product-pillar/SKILL.md +81 -0
  42. package/governance/skills/publication-policy/SKILL.md +63 -0
  43. package/governance/skills/role-and-candidate-authoring/SKILL.md +198 -0
  44. package/governance/skills/stakeholder-update/SKILL.md +177 -0
  45. package/governance/tooling/integrations.json +37 -0
  46. package/governance/venture-template/AGENT_ROLES_AND_DECISION_RIGHTS.md +58 -0
  47. package/governance/venture-template/COMPANY_VAULT_DEFINITION_2026-10-25.md +204 -0
  48. package/governance/venture-template/README.md +74 -0
  49. package/governance/venture-template/decision-rights.json +186 -0
  50. package/governance/venture-template/index.md +151 -0
  51. package/governance/venture-template/pillars/brand.md +209 -0
  52. package/governance/venture-template/pillars/business.md +255 -0
  53. package/governance/venture-template/pillars/product.md +254 -0
  54. package/governance/venture-template/render-roles.py +11 -0
  55. package/governance/venture-template/roles/README.md +85 -0
  56. package/governance/venture-template/roles/agent-roles.json +202 -0
  57. package/governance/venture-template/roles/ceo.md +163 -0
  58. package/governance/venture-template/roles/chief-of-staff.md +153 -0
  59. package/governance/venture-template/roles/cmo.md +165 -0
  60. package/governance/venture-template/roles/coo.md +189 -0
  61. package/governance/venture-template/roles/cto.md +158 -0
  62. package/governance/venture-template/roles/department-head.md +47 -0
  63. package/governance/venture-template/roles/forward-deployed-engineer.md +51 -0
  64. package/governance/venture-template/roles/founder.md +173 -0
  65. package/governance/venture-template/roles/hr-manager.md +49 -0
  66. package/governance/venture-template/roles/talent-acquisition-manager.md +50 -0
  67. package/governance/venture-template/roles/venture-roles.json +62 -0
  68. package/interface/acting.mjs +83 -0
  69. package/interface/adapters/claude-code.json +16 -0
  70. package/interface/adapters/codex.json +14 -0
  71. package/interface/adapters/cursor.json +16 -0
  72. package/interface/adapters.mjs +182 -0
  73. package/interface/api/server.mjs +71 -0
  74. package/interface/boundaries.mjs +218 -0
  75. package/interface/brand.mjs +214 -0
  76. package/interface/cli/find-venture.mjs +15 -0
  77. package/interface/cli/freshness.mjs +59 -0
  78. package/interface/cli/guard.mjs +6 -0
  79. package/interface/cli/mentura.mjs +341 -0
  80. package/interface/cli/naive.mjs +194 -0
  81. package/interface/context.mjs +524 -0
  82. package/interface/demo/board.mjs +581 -0
  83. package/interface/demo/decision-harness.mjs +91 -0
  84. package/interface/demo/live-agent.mjs +47 -0
  85. package/interface/dev/setup.mjs +84 -0
  86. package/interface/gate-hooks.mjs +36 -0
  87. package/interface/harness/evals.mjs +90 -0
  88. package/interface/harness/examples/pt-no-shows.dump.md +19 -0
  89. package/interface/harness/run.mjs +313 -0
  90. package/interface/harness/site.mjs +59 -0
  91. package/interface/instrumentation.mjs +86 -0
  92. package/interface/kit-boundary.mjs +73 -0
  93. package/interface/layer.json +37 -0
  94. package/interface/mcp/server.mjs +162 -0
  95. package/interface/operator-console/README.md +58 -0
  96. package/interface/package-routes.mjs +196 -0
  97. package/interface/provenance.mjs +65 -0
  98. package/interface/reads.mjs +173 -0
  99. package/interface/references/references.json +28 -0
  100. package/interface/release-card.mjs +42 -0
  101. package/interface/route-aliases.json +32 -0
  102. package/interface/router.mjs +653 -0
  103. package/interface/skills/install-mentura/SKILL.md +88 -0
  104. package/interface/skills/naive-setup/SKILL.md +71 -0
  105. package/interface/skills/naive-setup/artifact.html +83 -0
  106. package/interface/tooling/integrations.json +112 -0
  107. package/interface/vitals.mjs +115 -0
  108. package/interface/writes.mjs +190 -0
  109. package/knowledge/connectors/README.md +54 -0
  110. package/knowledge/connectors/catalog.mjs +118 -0
  111. package/knowledge/connectors/contract.mjs +56 -0
  112. package/knowledge/connectors/eval.mjs +176 -0
  113. package/knowledge/connectors/filesystem.mjs +79 -0
  114. package/knowledge/connectors/index.mjs +102 -0
  115. package/knowledge/connectors/resolve.mjs +44 -0
  116. package/knowledge/connectors/suite.mjs +80 -0
  117. package/knowledge/freshness.json +85 -0
  118. package/knowledge/layer.json +50 -0
  119. package/knowledge/skills/find-reference/SKILL.md +38 -0
  120. package/knowledge/skills/industry-awareness/SKILL.md +277 -0
  121. package/knowledge/templates/glossary.md +23 -0
  122. package/knowledge/tooling/integrations.json +127 -0
  123. package/knowledge/tooling/questions.md +46 -0
  124. package/operations/FOUNDER_OPERATING_MODEL_CAMPAIGN_2026-10-14.md +16 -0
  125. package/operations/departments.json +162 -0
  126. package/operations/handoff-v2.md +93 -0
  127. package/operations/layer.json +67 -0
  128. package/operations/marketing/check-public.mjs +68 -0
  129. package/operations/marketing/site/README.md +86 -0
  130. package/operations/marketing/site/matrix.json +731 -0
  131. package/operations/marketing/site/plan.mjs +117 -0
  132. package/operations/marketing/site/presence.mjs +252 -0
  133. package/operations/references/references.json +45 -0
  134. package/operations/skills/recruiting/SKILL.md +32 -0
  135. package/operations/skills/team-design/SKILL.md +36 -0
  136. package/operations/skills/tool-selection/SKILL.md +46 -0
  137. package/operations/templates/marketing-brand.md +46 -0
  138. package/operations/templates/public-assets.md +39 -0
  139. package/operations/templates/recruiting-brief.md +35 -0
  140. package/operations/tooling/integrations.json +158 -0
  141. package/package.json +59 -4
  142. package/packages/catalog.json +280 -0
  143. package/packages/cofounder/package.json +42 -0
  144. package/packages/counsel/package.json +47 -0
  145. package/packages/customers/package.json +41 -0
  146. package/packages/deals/package.json +27 -0
  147. package/packages/decisions/package.json +28 -0
  148. package/packages/engineering/package.json +41 -0
  149. package/packages/evidence/package.json +17 -0
  150. package/packages/finance/frames/fund-without-ceding-control.frame.json +177 -0
  151. package/packages/finance/package.json +48 -0
  152. package/packages/foundation/package.json +59 -0
  153. package/packages/fund/package.json +48 -0
  154. package/packages/investor/package.json +57 -0
  155. package/packages/investors/package.json +21 -0
  156. package/packages/legal/package.json +73 -0
  157. package/packages/marketing/package.json +63 -0
  158. package/packages/money/package.json +42 -0
  159. package/packages/operations/package.json +74 -0
  160. package/packages/partner/package.json +38 -0
  161. package/packages/people/package.json +64 -0
  162. package/packages/product/package.json +49 -0
  163. package/packages/raw/package.json +19 -0
  164. package/packages/relationships/package.json +52 -0
  165. package/packages/validation/package.json +112 -0
  166. package/platform/README.md +51 -0
  167. package/platform/adapter.json +15 -0
  168. package/platform/connectors/eval.mjs +154 -0
  169. package/platform/connectors/fake-github.mjs +91 -0
  170. package/platform/connectors/github.mjs +105 -0
  171. package/platform/integrations/jev.mjs +30 -0
  172. package/platform/integrations/langsmith-docket.mjs +53 -0
  173. package/platform/integrations/langsmith-setup.mjs +115 -0
  174. package/platform/integrations/langsmith.mjs +47 -0
  175. package/platform/integrations/remote-image.mjs +79 -0
  176. package/platform/integrations/rho.mjs +146 -0
  177. package/platform/integrations/site.mjs +75 -0
  178. package/platform/kits/README.md +14 -0
  179. package/platform/kits/agent-upgrade/kit.json +15 -0
  180. package/platform/kits/agent-upgrade/run.mjs +82 -0
  181. package/platform/kits/agent-upgrade/stand-in-agent.mjs +32 -0
  182. package/platform/kits/validation-kickoff/kit.json +29 -0
  183. package/platform/mcp/research.mjs +64 -0
  184. package/platform/webhooks/server.mjs +70 -0
  185. package/purpose/checklists/vault-and-tooling-consultancy-setup.md +216 -0
  186. package/purpose/frameworks/business-discovery-framework-amendments-2026-05-20.md +181 -0
  187. package/purpose/frameworks/business-discovery-framework.html +938 -0
  188. package/purpose/frameworks/business-discovery-framework.md +326 -0
  189. package/purpose/frameworks/idea-map/README.md +74 -0
  190. package/purpose/frameworks/idea-map/conventions.json +146 -0
  191. package/purpose/frameworks/idea-map/graph.mjs +182 -0
  192. package/purpose/frameworks/idea-map/ideamap.mjs +181 -0
  193. package/purpose/frameworks/idea-map/route.mjs +140 -0
  194. package/purpose/frameworks/onboarding-handoff-framework.md +269 -0
  195. package/purpose/frameworks/post-discovery-deliverable-playbook.md +274 -0
  196. package/purpose/frameworks/product-anatomy/README.md +63 -0
  197. package/purpose/frameworks/product-anatomy/anatomy.mjs +195 -0
  198. package/purpose/frameworks/product-anatomy/examples/polity.answers.reasoned.json +11 -0
  199. package/purpose/frameworks/product-anatomy/examples/polity.json +89 -0
  200. package/purpose/frameworks/product-anatomy/phases.json +107 -0
  201. package/purpose/frameworks/product-anatomy/questions.json +74 -0
  202. package/purpose/kits/validation/README.md +14 -0
  203. package/purpose/kits/validation/references/references.json +122 -0
  204. package/purpose/kits/validation/skills/problem-validation/SKILL.md +43 -0
  205. package/purpose/kits/validation/skills/validation-score/SKILL.md +35 -0
  206. package/purpose/kits/validation/templates/go-to-market.md +19 -0
  207. package/purpose/kits/validation/templates/operations-readiness.md +20 -0
  208. package/purpose/kits/validation/templates/pricing.md +22 -0
  209. package/purpose/kits/validation/templates/problem-interview.md +36 -0
  210. package/purpose/kits/validation/templates/problem.md +45 -0
  211. package/purpose/kits/validation/templates/product-market-fit.md +23 -0
  212. package/purpose/kits/validation/templates/solution.md +18 -0
  213. package/purpose/kits/validation/templates/team.md +19 -0
  214. package/purpose/kits/validation/weights.json +22 -0
  215. package/purpose/layer.json +53 -0
  216. package/purpose/references/references.json +324 -0
  217. package/purpose/skills/ambition-tier/SKILL.md +205 -0
  218. package/purpose/skills/telos/SKILL.md +52 -0
  219. package/purpose/strategic-foundation.json +133 -0
  220. package/purpose/templates/telos-based-smart.md +39 -0
  221. package/purpose/tooling/integrations.json +48 -0
  222. package/schema/README.md +62 -0
  223. package/schema/VERSION +1 -0
  224. package/schema/access.mjs +57 -0
  225. package/schema/account.schema.json +79 -0
  226. package/schema/adapter.schema.json +33 -0
  227. package/schema/add-department.mjs +41 -0
  228. package/schema/agent-capability.schema.json +94 -0
  229. package/schema/alignment.schema.json +61 -0
  230. package/schema/assess-problem.mjs +131 -0
  231. package/schema/assets.mjs +212 -0
  232. package/schema/bdd.mjs +56 -0
  233. package/schema/board.schema.json +101 -0
  234. package/schema/brand.mjs +373 -0
  235. package/schema/build-release.mjs +61 -0
  236. package/schema/caching.mjs +505 -0
  237. package/schema/check-assets.mjs +24 -0
  238. package/schema/check-boundary.mjs +54 -0
  239. package/schema/check-brand.mjs +24 -0
  240. package/schema/check-config.mjs +110 -0
  241. package/schema/check-data-model.mjs +170 -0
  242. package/schema/check-data-packages.mjs +83 -0
  243. package/schema/check-departments.mjs +45 -0
  244. package/schema/check-execution.mjs +85 -0
  245. package/schema/check-governance.mjs +98 -0
  246. package/schema/check-integrations.mjs +33 -0
  247. package/schema/check-nextjs-status.mjs +79 -0
  248. package/schema/check-nouns.mjs +49 -0
  249. package/schema/check-people.mjs +70 -0
  250. package/schema/check-private.mjs +61 -0
  251. package/schema/check-provenance.mjs +55 -0
  252. package/schema/check-publication.mjs +96 -0
  253. package/schema/check-ready.mjs +89 -0
  254. package/schema/check-sources.mjs +74 -0
  255. package/schema/check-team.mjs +94 -0
  256. package/schema/check-telos.mjs +59 -0
  257. package/schema/check-validation.mjs +165 -0
  258. package/schema/check-ventures.mjs +81 -0
  259. package/schema/check-where-used.mjs +58 -0
  260. package/schema/data-master.schema.json +14 -0
  261. package/schema/data-model.json +1584 -0
  262. package/schema/data-model.schema.json +152 -0
  263. package/schema/deal.schema.json +80 -0
  264. package/schema/decision-analytics.mjs +77 -0
  265. package/schema/decision-record.schema.json +290 -0
  266. package/schema/decision-rights.schema.json +34 -0
  267. package/schema/decision.schema.json +30 -0
  268. package/schema/decisions.mjs +327 -0
  269. package/schema/department-catalog.schema.json +55 -0
  270. package/schema/department.schema.json +15 -0
  271. package/schema/departments.mjs +32 -0
  272. package/schema/docket-eval.mjs +89 -0
  273. package/schema/docket.mjs +135 -0
  274. package/schema/eval-suite.schema.json +45 -0
  275. package/schema/evidence.schema.json +110 -0
  276. package/schema/examples/frames/judge.mjs +10 -0
  277. package/schema/examples/migrate/agent.mjs +12 -0
  278. package/schema/examples/venture-type/jev.mjs +12 -0
  279. package/schema/examples/workspace/ventures/acme-labs/AGENTS.md +17 -0
  280. package/schema/examples/workspace/ventures/acme-labs/accounts/.gitkeep +0 -0
  281. package/schema/examples/workspace/ventures/acme-labs/decisions/discovery-goal.decision.json +23 -0
  282. package/schema/examples/workspace/ventures/acme-labs/decisions/statement-icp.decision.json +22 -0
  283. package/schema/examples/workspace/ventures/acme-labs/decisions/statement-problem.decision.json +22 -0
  284. package/schema/examples/workspace/ventures/acme-labs/decisions/venture.team.decision.json +37 -0
  285. package/schema/examples/workspace/ventures/acme-labs/knowledge/README.md +3 -0
  286. package/schema/examples/workspace/ventures/acme-labs/knowledge/sources.json +46 -0
  287. package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000001.json +12 -0
  288. package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000002.json +12 -0
  289. package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000003.json +12 -0
  290. package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000004.json +12 -0
  291. package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000005.json +12 -0
  292. package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000006.json +12 -0
  293. package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000007.json +12 -0
  294. package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000008.json +12 -0
  295. package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000009.json +12 -0
  296. package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000010.json +12 -0
  297. package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000011.json +12 -0
  298. package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000012.json +12 -0
  299. package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000013.json +12 -0
  300. package/schema/examples/workspace/ventures/acme-labs/money/30000000-0000-4000-8000-000000000014.json +12 -0
  301. package/schema/examples/workspace/ventures/acme-labs/money/_streams/transactions/stream.json +5 -0
  302. package/schema/examples/workspace/ventures/acme-labs/money/_summary.json +30 -0
  303. package/schema/examples/workspace/ventures/acme-labs/operations/departments/customer-service/README.md +5 -0
  304. package/schema/examples/workspace/ventures/acme-labs/operations/departments/customer-service/department.json +5 -0
  305. package/schema/examples/workspace/ventures/acme-labs/operations/departments/finance/README.md +5 -0
  306. package/schema/examples/workspace/ventures/acme-labs/operations/departments/finance/department.json +5 -0
  307. package/schema/examples/workspace/ventures/acme-labs/operations/departments/human-capital/README.md +5 -0
  308. package/schema/examples/workspace/ventures/acme-labs/operations/departments/human-capital/department.json +5 -0
  309. package/schema/examples/workspace/ventures/acme-labs/operations/departments/it/README.md +5 -0
  310. package/schema/examples/workspace/ventures/acme-labs/operations/departments/it/department.json +5 -0
  311. package/schema/examples/workspace/ventures/acme-labs/operations/departments/marketing-and-sales/README.md +5 -0
  312. package/schema/examples/workspace/ventures/acme-labs/operations/departments/marketing-and-sales/department.json +5 -0
  313. package/schema/examples/workspace/ventures/acme-labs/operations/departments/products/README.md +5 -0
  314. package/schema/examples/workspace/ventures/acme-labs/operations/departments/products/department.json +5 -0
  315. package/schema/examples/workspace/ventures/acme-labs/operations/departments/strategy/README.md +5 -0
  316. package/schema/examples/workspace/ventures/acme-labs/operations/departments/strategy/department.json +5 -0
  317. package/schema/examples/workspace/ventures/acme-labs/people/founders/maya-chen.json +15 -0
  318. package/schema/examples/workspace/ventures/acme-labs/people/people.json +22 -0
  319. package/schema/examples/workspace/ventures/acme-labs/pillars/brand.md +5 -0
  320. package/schema/examples/workspace/ventures/acme-labs/pillars/business.md +5 -0
  321. package/schema/examples/workspace/ventures/acme-labs/pillars/product.md +5 -0
  322. package/schema/examples/workspace/ventures/acme-labs/products/.gitkeep +0 -0
  323. package/schema/examples/workspace/ventures/acme-labs/products/decisions/product.core-product.decision.json +28 -0
  324. package/schema/examples/workspace/ventures/acme-labs/products/decisions/product.exists.decision.json +38 -0
  325. package/schema/examples/workspace/ventures/acme-labs/products/decisions/product.flagship.decision.json +27 -0
  326. package/schema/examples/workspace/ventures/acme-labs/profile.md +12 -0
  327. package/schema/examples/workspace/ventures/acme-labs/provenance.jsonl +12 -0
  328. package/schema/examples/workspace/ventures/acme-labs/raw/dump.md +6 -0
  329. package/schema/examples/workspace/ventures/acme-labs/venture.json +27 -0
  330. package/schema/examples/workspace/ventures/northstar/AGENTS.md +17 -0
  331. package/schema/examples/workspace/ventures/northstar/accounts/.gitkeep +0 -0
  332. package/schema/examples/workspace/ventures/northstar/deals/ductline.json +8 -0
  333. package/schema/examples/workspace/ventures/northstar/deals/kilnworks.json +14 -0
  334. package/schema/examples/workspace/ventures/northstar/decisions/venture.team.decision.json +31 -0
  335. package/schema/examples/workspace/ventures/northstar/evidence/README.md +3 -0
  336. package/schema/examples/workspace/ventures/northstar/fund/decisions/terms.decision.json +27 -0
  337. package/schema/examples/workspace/ventures/northstar/fund/decisions/terms.first-close.decision.json +27 -0
  338. package/schema/examples/workspace/ventures/northstar/fund/decisions/thesis.decision.json +26 -0
  339. package/schema/examples/workspace/ventures/northstar/fund/terms.json +22 -0
  340. package/schema/examples/workspace/ventures/northstar/fund/thesis.json +22 -0
  341. package/schema/examples/workspace/ventures/northstar/governance/README.md +3 -0
  342. package/schema/examples/workspace/ventures/northstar/governance/departments/external-relationships/charter.md +5 -0
  343. package/schema/examples/workspace/ventures/northstar/governance/departments/external-relationships/department.json +5 -0
  344. package/schema/examples/workspace/ventures/northstar/governance/departments/finance/charter.md +5 -0
  345. package/schema/examples/workspace/ventures/northstar/governance/departments/finance/department.json +5 -0
  346. package/schema/examples/workspace/ventures/northstar/governance/departments/human-capital/charter.md +5 -0
  347. package/schema/examples/workspace/ventures/northstar/governance/departments/human-capital/department.json +5 -0
  348. package/schema/examples/workspace/ventures/northstar/governance/departments/risk-and-compliance/charter.md +5 -0
  349. package/schema/examples/workspace/ventures/northstar/governance/departments/risk-and-compliance/department.json +5 -0
  350. package/schema/examples/workspace/ventures/northstar/governance/departments/strategy/charter.md +5 -0
  351. package/schema/examples/workspace/ventures/northstar/governance/departments/strategy/department.json +5 -0
  352. package/schema/examples/workspace/ventures/northstar/knowledge/README.md +3 -0
  353. package/schema/examples/workspace/ventures/northstar/operations/README.md +3 -0
  354. package/schema/examples/workspace/ventures/northstar/operations/departments/external-relationships/README.md +5 -0
  355. package/schema/examples/workspace/ventures/northstar/operations/departments/external-relationships/department.json +5 -0
  356. package/schema/examples/workspace/ventures/northstar/operations/departments/finance/README.md +5 -0
  357. package/schema/examples/workspace/ventures/northstar/operations/departments/finance/department.json +5 -0
  358. package/schema/examples/workspace/ventures/northstar/operations/departments/human-capital/README.md +5 -0
  359. package/schema/examples/workspace/ventures/northstar/operations/departments/human-capital/department.json +5 -0
  360. package/schema/examples/workspace/ventures/northstar/operations/departments/risk-and-compliance/README.md +5 -0
  361. package/schema/examples/workspace/ventures/northstar/operations/departments/risk-and-compliance/department.json +5 -0
  362. package/schema/examples/workspace/ventures/northstar/operations/departments/strategy/README.md +5 -0
  363. package/schema/examples/workspace/ventures/northstar/operations/departments/strategy/department.json +5 -0
  364. package/schema/examples/workspace/ventures/northstar/people/founders/ana-ruiz.json +13 -0
  365. package/schema/examples/workspace/ventures/northstar/people/founders/ben-okafor.json +7 -0
  366. package/schema/examples/workspace/ventures/northstar/people/people.json +27 -0
  367. package/schema/examples/workspace/ventures/northstar/pillars/brand.md +5 -0
  368. package/schema/examples/workspace/ventures/northstar/pillars/business.md +5 -0
  369. package/schema/examples/workspace/ventures/northstar/pillars/product.md +5 -0
  370. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/AGENTS.md +17 -0
  371. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/accounts/.gitkeep +0 -0
  372. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/decisions/venture.team.decision.json +31 -0
  373. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/knowledge/README.md +3 -0
  374. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/customer-service/README.md +5 -0
  375. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/customer-service/department.json +5 -0
  376. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/finance/README.md +5 -0
  377. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/finance/department.json +5 -0
  378. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/human-capital/README.md +5 -0
  379. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/human-capital/department.json +5 -0
  380. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/it/README.md +5 -0
  381. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/it/department.json +5 -0
  382. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/marketing-and-sales/README.md +5 -0
  383. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/marketing-and-sales/department.json +5 -0
  384. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/products/README.md +5 -0
  385. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/products/department.json +5 -0
  386. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/strategy/README.md +5 -0
  387. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/operations/departments/strategy/department.json +5 -0
  388. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/pillars/brand.md +5 -0
  389. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/pillars/business.md +5 -0
  390. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/pillars/product.md +5 -0
  391. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/products/.gitkeep +0 -0
  392. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/products/decisions/product.core-product.decision.json +28 -0
  393. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/products/decisions/product.exists.decision.json +32 -0
  394. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/products/decisions/product.flagship.decision.json +27 -0
  395. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/profile.md +5 -0
  396. package/schema/examples/workspace/ventures/northstar/portfolio/kilnworks/venture.json +27 -0
  397. package/schema/examples/workspace/ventures/northstar/products/.gitkeep +0 -0
  398. package/schema/examples/workspace/ventures/northstar/profile.md +5 -0
  399. package/schema/examples/workspace/ventures/northstar/provenance.jsonl +3 -0
  400. package/schema/examples/workspace/ventures/northstar/purpose/README.md +3 -0
  401. package/schema/examples/workspace/ventures/northstar/venture.json +49 -0
  402. package/schema/examples/workspace/ventures/studio/accounts/bakery/account.json +18 -0
  403. package/schema/examples/workspace/ventures/studio/evidence/.gitkeep +0 -0
  404. package/schema/examples/workspace/ventures/studio/governance/.gitkeep +0 -0
  405. package/schema/examples/workspace/ventures/studio/knowledge/.gitkeep +0 -0
  406. package/schema/examples/workspace/ventures/studio/operations/.gitkeep +0 -0
  407. package/schema/examples/workspace/ventures/studio/print/knowledge/.gitkeep +0 -0
  408. package/schema/examples/workspace/ventures/studio/print/purpose/.gitkeep +0 -0
  409. package/schema/examples/workspace/ventures/studio/print/venture.json +37 -0
  410. package/schema/examples/workspace/ventures/studio/products/brand-care.json +19 -0
  411. package/schema/examples/workspace/ventures/studio/products/brand-kit.json +23 -0
  412. package/schema/examples/workspace/ventures/studio/provenance.jsonl +3 -0
  413. package/schema/examples/workspace/ventures/studio/purpose/.gitkeep +0 -0
  414. package/schema/examples/workspace/ventures/studio/venture.json +43 -0
  415. package/schema/examples/workspace/ventures/tandem/accounts/.gitkeep +0 -0
  416. package/schema/examples/workspace/ventures/tandem/evidence/README.md +3 -0
  417. package/schema/examples/workspace/ventures/tandem/governance/README.md +3 -0
  418. package/schema/examples/workspace/ventures/tandem/governance/agreements/dee-fast.md +3 -0
  419. package/schema/examples/workspace/ventures/tandem/governance/board.json +51 -0
  420. package/schema/examples/workspace/ventures/tandem/governance/departments/finance/charter.md +5 -0
  421. package/schema/examples/workspace/ventures/tandem/governance/departments/finance/department.json +5 -0
  422. package/schema/examples/workspace/ventures/tandem/governance/departments/primary/marketing-and-sales/charter.md +5 -0
  423. package/schema/examples/workspace/ventures/tandem/governance/departments/primary/marketing-and-sales/department.json +5 -0
  424. package/schema/examples/workspace/ventures/tandem/governance/exit.json +33 -0
  425. package/schema/examples/workspace/ventures/tandem/governance/founder-agreement.json +24 -0
  426. package/schema/examples/workspace/ventures/tandem/governance/founder-agreement.md +3 -0
  427. package/schema/examples/workspace/ventures/tandem/governance/ownership.json +131 -0
  428. package/schema/examples/workspace/ventures/tandem/governance/publication-policy.json +171 -0
  429. package/schema/examples/workspace/ventures/tandem/knowledge/README.md +3 -0
  430. package/schema/examples/workspace/ventures/tandem/knowledge/research/friends-feedback-2026-08.md +3 -0
  431. package/schema/examples/workspace/ventures/tandem/knowledge/research/pilot-2026-10.md +3 -0
  432. package/schema/examples/workspace/ventures/tandem/knowledge/research/problem-interviews-2026-09.md +20 -0
  433. package/schema/examples/workspace/ventures/tandem/knowledge/sources.json +8 -0
  434. package/schema/examples/workspace/ventures/tandem/knowledge/where-used.json +13 -0
  435. package/schema/examples/workspace/ventures/tandem/operations/README.md +3 -0
  436. package/schema/examples/workspace/ventures/tandem/operations/departments/finance/README.md +5 -0
  437. package/schema/examples/workspace/ventures/tandem/operations/departments/finance/department.json +5 -0
  438. package/schema/examples/workspace/ventures/tandem/operations/departments/primary/marketing-and-sales/README.md +5 -0
  439. package/schema/examples/workspace/ventures/tandem/operations/departments/primary/marketing-and-sales/department.json +5 -0
  440. package/schema/examples/workspace/ventures/tandem/pillars/brand.md +5 -0
  441. package/schema/examples/workspace/ventures/tandem/pillars/business.md +5 -0
  442. package/schema/examples/workspace/ventures/tandem/pillars/product.md +5 -0
  443. package/schema/examples/workspace/ventures/tandem/products/.gitkeep +0 -0
  444. package/schema/examples/workspace/ventures/tandem/products/shop-app.json +22 -0
  445. package/schema/examples/workspace/ventures/tandem/profile.md +5 -0
  446. package/schema/examples/workspace/ventures/tandem/provenance.jsonl +13 -0
  447. package/schema/examples/workspace/ventures/tandem/purpose/README.md +3 -0
  448. package/schema/examples/workspace/ventures/tandem/purpose/telos.json +78 -0
  449. package/schema/examples/workspace/ventures/tandem/purpose/validation/go-to-market.json +35 -0
  450. package/schema/examples/workspace/ventures/tandem/purpose/validation/pricing.json +37 -0
  451. package/schema/examples/workspace/ventures/tandem/purpose/validation/problem.json +74 -0
  452. package/schema/examples/workspace/ventures/tandem/purpose/validation/product-market-fit.json +66 -0
  453. package/schema/examples/workspace/ventures/tandem/purpose/validation/solution.json +33 -0
  454. package/schema/examples/workspace/ventures/tandem/purpose/validation/team.json +52 -0
  455. package/schema/examples/workspace/ventures/tandem/venture.json +79 -0
  456. package/schema/examples/workspace/ventures/woodworking/knowledge/.gitkeep +0 -0
  457. package/schema/examples/workspace/ventures/woodworking/venture.json +21 -0
  458. package/schema/examples/workspace/workspace.json +8 -0
  459. package/schema/exit.schema.json +74 -0
  460. package/schema/foundation.mjs +118 -0
  461. package/schema/founder-agreement.schema.json +83 -0
  462. package/schema/founder.schema.json +72 -0
  463. package/schema/frame.schema.json +240 -0
  464. package/schema/frames.mjs +228 -0
  465. package/schema/freshness-log.schema.json +40 -0
  466. package/schema/freshness-profiles.schema.json +44 -0
  467. package/schema/freshness.mjs +473 -0
  468. package/schema/fund-terms.schema.json +107 -0
  469. package/schema/fund-thesis.schema.json +87 -0
  470. package/schema/gate-drafts.mjs +130 -0
  471. package/schema/gate.mjs +133 -0
  472. package/schema/glossary.schema.json +25 -0
  473. package/schema/handoff.schema.json +239 -0
  474. package/schema/icp.schema.json +136 -0
  475. package/schema/idea-graph.schema.json +61 -0
  476. package/schema/init-venture.mjs +128 -0
  477. package/schema/integration.schema.json +143 -0
  478. package/schema/judgment.schema.json +38 -0
  479. package/schema/kit.schema.json +47 -0
  480. package/schema/kits.mjs +24 -0
  481. package/schema/knowledge-sources.schema.json +260 -0
  482. package/schema/layer.json +59 -0
  483. package/schema/layer.schema.json +119 -0
  484. package/schema/load-packages.mjs +26 -0
  485. package/schema/marketing-brand.schema.json +116 -0
  486. package/schema/marketing-sitemap.schema.json +232 -0
  487. package/schema/migrate-venture.mjs +192 -0
  488. package/schema/migrations/kit-to-package.mjs +94 -0
  489. package/schema/migrations/package-layout.mjs +86 -0
  490. package/schema/model-steps.mjs +717 -0
  491. package/schema/money-summary.schema.json +138 -0
  492. package/schema/naive-config.mjs +165 -0
  493. package/schema/naive-config.schema.json +268 -0
  494. package/schema/need-now.json +772 -0
  495. package/schema/need-now.mjs +108 -0
  496. package/schema/nextjs-status.schema.json +29 -0
  497. package/schema/ownership.schema.json +282 -0
  498. package/schema/packages.json +4 -0
  499. package/schema/packages.mjs +252 -0
  500. package/schema/payouts.mjs +91 -0
  501. package/schema/people.schema.json +86 -0
  502. package/schema/port-venture.mjs +161 -0
  503. package/schema/product.schema.json +223 -0
  504. package/schema/provenance-entry.schema.json +154 -0
  505. package/schema/public-assets.schema.json +76 -0
  506. package/schema/publication-policy.schema.json +293 -0
  507. package/schema/raw-source.schema.json +15 -0
  508. package/schema/reference.schema.json +115 -0
  509. package/schema/release-read.mjs +111 -0
  510. package/schema/releases.mjs +497 -0
  511. package/schema/route-aliases.schema.json +25 -0
  512. package/schema/run-handoff.mjs +51 -0
  513. package/schema/scaffold/defaults.json +54 -0
  514. package/schema/setup.mjs +164 -0
  515. package/schema/site-presence.schema.json +337 -0
  516. package/schema/stale.mjs +111 -0
  517. package/schema/start-release.mjs +41 -0
  518. package/schema/strategic-foundation.schema.json +74 -0
  519. package/schema/stream.schema.json +14 -0
  520. package/schema/structure.mjs +186 -0
  521. package/schema/systems-catalog.json +126 -0
  522. package/schema/systems-catalog.schema.json +78 -0
  523. package/schema/team-plan.schema.json +157 -0
  524. package/schema/telos.schema.json +197 -0
  525. package/schema/time-to-ready.mjs +93 -0
  526. package/schema/trigger.schema.json +44 -0
  527. package/schema/triggers.mjs +237 -0
  528. package/schema/upgrade-registry.schema.json +60 -0
  529. package/schema/upgrade-venture.mjs +107 -0
  530. package/schema/upgrades.json +69 -0
  531. package/schema/upgrades.mjs +385 -0
  532. package/schema/use-model.mjs +10 -0
  533. package/schema/validate.py +58 -0
  534. package/schema/validation-evidence.schema.json +123 -0
  535. package/schema/validation-go-to-market.schema.json +110 -0
  536. package/schema/validation-operations-readiness.schema.json +91 -0
  537. package/schema/validation-pricing.schema.json +114 -0
  538. package/schema/validation-problem.schema.json +118 -0
  539. package/schema/validation-product-market-fit.schema.json +135 -0
  540. package/schema/validation-solution.schema.json +79 -0
  541. package/schema/validation-team.schema.json +116 -0
  542. package/schema/venture-role.schema.json +64 -0
  543. package/schema/venture-type.mjs +62 -0
  544. package/schema/venture-types.json +346 -0
  545. package/schema/venture-types.schema.json +129 -0
  546. package/schema/venture.schema.json +460 -0
  547. package/schema/vocab/business-models.json +298 -0
  548. package/schema/where-used.schema.json +56 -0
  549. package/schema/workspace.schema.json +38 -0
@@ -0,0 +1,190 @@
1
+ // What a write answers, what a layout offers, and the wrapper the router puts around every call
2
+ // (Next.js translation M-3, M-9, M-17; it also wires in interface/reads.mjs, interface/boundaries.mjs
3
+ // and interface/package-routes.mjs).
4
+ //
5
+ // changed M-3 one round trip: a write answers with the new state and what it changed: the
6
+ // record's state before and after, each package whose progress or state moved,
7
+ // the records that move now because of it, what it unlocked (a package that
8
+ // completed and what that opens, a decision that no longer waits) and the
9
+ // need-now rows it settled
10
+ // next M-17 a write answers with the next route to act on: accept the draft it made, else
11
+ // the next record in the same package that moves now, else what it unlocked,
12
+ // else what need-now suggests, else the decision that matters most
13
+ // actions M-9 a layout lists the actions available in its folder to the actor asking: each
14
+ // route's actions, which of them this actor may take now, and whether a write
15
+ // lands as the record, as a draft, or is locked or refused, with why
16
+ //
17
+ // around() runs before and after the router's own call: the framework and venture global errors
18
+ // first (E-8), then package, group and venture routes (L-6, P-36, P-3), then the call, then what
19
+ // reads and layouts add (L-9, L-10, O-1 to O-3, P-28) and what writes answer (M-3, M-17). It never
20
+ // calls a model.
21
+
22
+ import { allowed, draftOf, gateOf, resolve, routes, ventureDir } from "./router.mjs";
23
+ import { frameworkHealth, globalError, ventureHealth } from "./boundaries.mjs";
24
+ import { groupLayout, packageOfRecord, routeOfType, specialCall } from "./package-routes.mjs";
25
+ import { filterRead, hierarchy, metadataFor, resolvedBy, withParams } from "./reads.mjs";
26
+
27
+ /** The state a write can change, taken before and after it. */
28
+ export async function snapshot(dir) {
29
+ const safe = async (f) => { try { return await f(); } catch { return null; } };
30
+ const { packages } = await import("../schema/packages.mjs");
31
+ const { needNow } = await import("../schema/need-now.mjs");
32
+ const { decisions } = await import("../schema/decisions.mjs");
33
+ return { packages: await safe(() => packages(dir)), need: await safe(() => needNow(dir)), decisions: await safe(() => decisions(dir)) };
34
+ }
35
+
36
+ const pkgMap = (s) => new Map([...(s?.packages?.core ?? []), ...(s?.packages?.handoff ?? [])].map((p) => [p.id, p]));
37
+ const rowOf = (s, type) => (s?.packages?.core ?? []).flatMap((p) => p.records).find((x) => x.record === type && x.role !== "reads") ?? null;
38
+
39
+ /** What a write changed (M-3), from the state before and after it. */
40
+ export async function changed(before, after, { record }) {
41
+ const b = pkgMap(before), a = pkgMap(after);
42
+ const packages = [...a.values()].filter((p) => { const o = b.get(p.id); return !o || o.progress !== p.progress || o.state !== p.state; })
43
+ .map((p) => ({ package: p.id, title: p.title, progress: { from: b.get(p.id)?.progress ?? null, to: p.progress }, state: { from: b.get(p.id)?.state ?? null, to: p.state } }));
44
+ const was = new Map((before?.packages?.core ?? []).flatMap((p) => p.records.map((x) => [`${p.id}:${x.record}`, x])));
45
+ const moves = [];
46
+ for (const p of after?.packages?.core ?? []) for (const x of p.records) {
47
+ const o = was.get(`${p.id}:${x.record}`);
48
+ if (x.role !== "reads" && x.moves === "now" && o?.moves === "waits" && !moves.some((m) => m.record === x.record)) moves.push({ record: x.record, package: p.id, route: await routeOfType(x.record) });
49
+ }
50
+ const unlocked = [...a.values()].filter((p) => p.state === "complete" && b.get(p.id)?.state !== "complete").map((p) => ({ package: p.id, unlocks: p.unlocks ?? [] }));
51
+ const dBefore = new Map((before?.decisions?.decisions ?? []).map((d) => [d.id, d.render]));
52
+ const freed = (after?.decisions?.decisions ?? []).filter((d) => ["waiting", "later"].includes(dBefore.get(d.id)) && ["now", "overdue"].includes(d.render)).map((d) => ({ decision: d.id, question: d.question ?? null }));
53
+ const nBefore = new Map((before?.need?.rows ?? []).map((x) => [x.type, x.status]));
54
+ const need = (after?.need?.rows ?? []).filter((x) => nBefore.has(x.type) && nBefore.get(x.type) !== x.status).map((x) => ({ record: x.type, from: nBefore.get(x.type), to: x.status }));
55
+ return {
56
+ record: { type: record, from: rowOf(before, record)?.status ?? null, to: rowOf(after, record)?.status ?? null, package: packageOfRecord(record) },
57
+ packages, moves_now: moves, unlocked: [...unlocked, ...freed], need_now: need,
58
+ };
59
+ }
60
+
61
+ const commandFor = (route, venture) => route?.endsWith("[id]") ? `mentura ${route.replace("[id]", "<id>")} write --venture ${venture} --input <file>` : `mentura ${route} write --venture ${venture} --input <file>`;
62
+
63
+ /** The next route to act on after a write (M-17). */
64
+ export async function next(after, { record, route, draft, venture, change }) {
65
+ if (draft) return { route, action: "accept", who: "a person", command: `mentura accept ${route} --venture ${venture}`, why: "the write is held as a draft until a person accepts it" };
66
+ const home = packageOfRecord(record);
67
+ const p = (after?.packages?.core ?? []).find((x) => x.id === home);
68
+ const missing = (p?.records ?? []).filter((x) => x.role !== "reads" && x.record !== record && x.moves === "now" && x.status === "missing");
69
+ const required = missing.find((x) => x.role === "required");
70
+ if (required) { const rt = await routeOfType(required.record); return { route: rt, action: "write", command: commandFor(rt, venture), why: `the next required record in ${p.title} that moves now` }; }
71
+ // An agent's write waits for a person before what reads it can move.
72
+ const review = (p?.records ?? []).filter((x) => x.role !== "reads" && x.status === "agent");
73
+ if (review.length) { const rt = await routeOfType(review[0].record); return { route: rt, action: "review", who: "a person", command: `mentura ${rt} read --venture ${venture}`, why: `${review[0].title} was written by an agent: a person confirms it by writing it with --as person, and what reads it moves then` }; }
74
+ const freed = change?.moves_now?.[0];
75
+ if (freed) return { route: freed.route, action: "write", command: commandFor(freed.route, venture), why: `this write let ${freed.record} move` };
76
+ if (missing.length) { const rt = await routeOfType(missing[0].record); return { route: rt, action: "write", command: commandFor(rt, venture), why: `the next ${missing[0].role} record in ${p.title} that moves now` }; }
77
+ const suggest = (after?.need?.rows ?? []).filter((x) => x.status === "suggest now" && x.type !== record).sort((a, b) => b.now - a.now)[0];
78
+ if (suggest) { const rt = await routeOfType(suggest.type); if (rt) return { route: rt, action: "write", command: commandFor(rt, venture), why: `need-now suggests it now: ${suggest.why}` }; }
79
+ const { rank } = await import("../schema/decisions.mjs");
80
+ const ds = after?.decisions ? rank(after.decisions.decisions, after.decisions.phase).filter((d) => d.rank != null && d.source === "file") : [];
81
+ if (ds.length) return { route: "governance/decisions", action: "decide", who: "a person", command: `mentura decide ${ds[0].id} --venture ${venture} --by <your name>`, why: ds[0].question };
82
+ return { route: null, action: null, why: "nothing waits on this write" };
83
+ }
84
+
85
+ /** The actions available in a route's folder to one actor (M-9). */
86
+ export async function actionsIn({ dir, r, route, actor = "agent", actor_id, venture }) {
87
+ const { holding, locked } = await import("../schema/decisions.mjs");
88
+ const { configOf, hold } = await import("../schema/gate.mjs");
89
+ const folder = r.collection === "item" ? r.route.slice(0, -"/[id]".length) : route.includes("/") ? route.slice(0, route.lastIndexOf("/")) : route;
90
+ const rs = routes().filter((x) => x.route === folder || x.route.startsWith(`${folder}/`));
91
+ const list = [];
92
+ for (const x of rs) {
93
+ const concrete = x.route === r.route ? route : x.route;
94
+ const entry = { route: concrete, kind: x.kind, actions: x.actions };
95
+ if (!dir) { list.push(entry); continue; }
96
+ const reads = allowed(dir, actor_id, "read", concrete);
97
+ const avail = x.actions.filter((a) => !["write", "run"].includes(a) && (reads || a === "template" || a === "layout"));
98
+ if (x.actions.includes("run")) {
99
+ const lock = actor !== "person" ? locked(dir, concrete, actor_id) : null;
100
+ if (reads && !lock) avail.push("run");
101
+ entry.run = lock ? { runs: false, why: `locked until decision ${lock.id} is made` } : reads ? { runs: true } : { runs: false, why: "the team plan does not let this role reach it" };
102
+ }
103
+ if (x.actions.includes("write")) {
104
+ let w;
105
+ if (!allowed(dir, actor_id, "write", concrete)) w = { lands: null, why: `${actor_id} may not write it (team plan routes)` };
106
+ else {
107
+ const lock = actor !== "person" ? locked(dir, concrete, actor_id) : null;
108
+ if (lock) w = { lands: null, why: `locked until decision ${lock.id} is made: ${lock.question}` };
109
+ else if (actor === "person") w = { lands: "record", why: "a person's write goes straight to the record" };
110
+ else {
111
+ const gate = gateOf(x, dir);
112
+ const d = !concrete.includes("[id]") ? draftOf(concrete, venture, undefined) : null;
113
+ const held = d ? holding(dir, d.file) : null;
114
+ const cfg = hold(configOf(dir), x.route, actor);
115
+ w = gate?.drafts ? { lands: "draft", why: `${x.record} is a ${gate.class} decision (${gate.level})` } : held ? { lands: "draft", why: `decision ${held.id} ${held.why}` } : cfg ? { lands: "draft", why: `${cfg.rule} fails closed in naive.config.json` } : { lands: "record", why: "no gate holds it" };
116
+ }
117
+ }
118
+ if (w.lands) avail.push("write");
119
+ entry.write = w;
120
+ }
121
+ entry.available = avail;
122
+ list.push(entry);
123
+ }
124
+ return { folder, actor: { kind: actor, ...(actor_id ? { id: actor_id } : {}) }, routes: list };
125
+ }
126
+
127
+ // The venture-relative file behind a read's source address (venture://path), for local sources only.
128
+ const fileOfAddress = (a) => a?.startsWith("venture://") ? a.slice("venture://".length) : null;
129
+
130
+ /**
131
+ * The wrapper around the router's call (see the top of this file). inner is the router's own call;
132
+ * args are what call takes. Returns what call returns, with the additions above.
133
+ */
134
+ export async function around(inner, args) {
135
+ const { venture, route, action, input, actor = "agent", workspace } = args;
136
+ const actor_id = args.actor_id ?? process.env.MENTURA_ACTOR_ID;
137
+ // E-8: a broken framework file stops every route, with its recovery steps.
138
+ const fw = frameworkHealth();
139
+ if (fw) return globalError(fw);
140
+ let res;
141
+ try { res = resolve(route); } catch (e) { return globalError({ scope: "framework", file: "schema/data-model.json", problem: `the route table cannot be built: ${String(e.message ?? e).split("\n")[0]}`, recovery: ["Check the data model and the manifests: node schema/check-data-model.mjs", "Restore the file it names from git"] }); }
142
+ const dir = venture ? ventureDir(venture, workspace) : null;
143
+ // E-8: a broken venture.json answers with recovery steps, before anything reads it.
144
+ if (dir) { const h = ventureHealth(dir); if (h) return globalError(h, venture); }
145
+ // L-6, P-36, P-3: package, group and venture routes are answered here, outside the route table.
146
+ if (!res.r) return (await specialCall(args)) ?? inner(args);
147
+ const r = res.r, params = res.params;
148
+ const concrete = r.collection === "item" ? r.route.replace("[id]", params.id) : r.route;
149
+ const before = action === "write" && dir && r.kind === "record" ? await snapshot(dir) : null;
150
+ let out;
151
+ try { out = await inner(args); }
152
+ catch (e) {
153
+ // E-6: a record that cannot be read is that record's error, not a crash.
154
+ if (action !== "read") throw e;
155
+ return { ok: false, outcome: "error", error: `${concrete}: the record does not parse: ${String(e.message ?? e).split("\n")[0]}`, fix: { schema: r.schema ?? null, template: r.template ?? null }, resolved_by: "error" };
156
+ }
157
+
158
+ if (action === "read" && dir && r.kind === "record") {
159
+ if (out.ok) {
160
+ const items = r.collection === "list" ? Object.fromEntries(Object.entries(out.sources ?? {}).map(([id, s]) => [id, fileOfAddress(s.address)]).filter(([, f]) => f))
161
+ : out.source && fileOfAddress(out.source.address) ? { [params.id ?? r.record]: fileOfAddress(out.source.address) } : {};
162
+ out = { ...out, params, metadata: await metadataFor({ dir, r, route: concrete, params, result: out.result, items }) };
163
+ out = filterRead(out, input?.where, r);
164
+ }
165
+ out.resolved_by = resolvedBy(out);
166
+ if (input?.hierarchy) {
167
+ const { layout } = await import("./router.mjs");
168
+ const pkg = packageOfRecord(r.record);
169
+ const lay = withParams(layout(r, dir), { dir, params, file: draftOf(concrete, venture, workspace)?.file, group: pkg ? groupLayout(pkg, dir) : null });
170
+ out.hierarchy = hierarchy({ answer: out, layout: lay }).levels;
171
+ }
172
+ return out;
173
+ }
174
+
175
+ if (action === "layout" && out.ok) {
176
+ const pkg = r.kind === "record" ? packageOfRecord(r.record) : null;
177
+ const file = dir && r.kind === "record" && r.collection !== "list" && !concrete.includes("[id]") ? draftOf(concrete, venture, workspace)?.file : null;
178
+ const result = withParams(out.result, { dir, params, file, group: pkg ? groupLayout(pkg, dir) : null });
179
+ result.actions = await actionsIn({ dir, r, route: concrete, actor, actor_id, venture: venture ?? null });
180
+ return { ...out, result };
181
+ }
182
+
183
+ if (action === "write" && before && out.ok && out.result?.written) {
184
+ const after = await snapshot(dir);
185
+ const change = await changed(before, after, { record: r.record });
186
+ out.result.changed = change;
187
+ out.result.next = await next(after, { record: r.record, route: concrete, draft: out.result.draft, venture, change });
188
+ }
189
+ return out;
190
+ }
@@ -0,0 +1,54 @@
1
+ ---
2
+ type: reference
3
+ source: hybrid
4
+ created: 2026-10-28T01:10:00Z
5
+ ai-model: claude-opus-5.5
6
+ tags: [everything-ecosystem, knowledge, connectors, storage]
7
+ description: "How the knowledge layer reaches the sources a venture keeps records in: the connector contract, the source map, the catalog and the evals (EE-037)."
8
+ ---
9
+
10
+ # Knowledge connectors
11
+
12
+ The knowledge layer is where every record is stored and found. It does not copy records into one place. Each venture keeps its records wherever it already works, and Mentura reaches each source through a connector (EE-037).
13
+
14
+ ## The parts
15
+
16
+ | Part | File | What it does |
17
+ |---|---|---|
18
+ | Contract | `contract.mjs` | The five things every connector does: `list`, `read`, `version`, `search`, `write`. A version is the git blob SHA-1 of the content in every source. |
19
+ | Registry | `../tooling/integrations.json` | An integration with a `connector` names the module that implements the contract for it. |
20
+ | Source map | `<venture>/knowledge/sources.json` | Optional. The venture's sources, and which record type lives in which. Unnamed record types stay in the venture folder. Schema: `schema/knowledge-sources.schema.json`; checked by `schema/check-sources.mjs`. |
21
+ | Connectors | `filesystem.mjs` | A folder or git repository (writes in place). The framework ships only this one: it works offline and names no vendor. Connectors that reach a vendor, such as GitHub, are platform and live in `platform/connectors/` (EE-041). |
22
+ | Matrix | `resolve.mjs` | The source-of-truth matrix (EE-042): `rules` in the source map name a system of record and owner per record type, attribute and context (phase, product, account, actor). The most specific rule wins; a tie is an error. `records` is shorthand for rules that match anything. An attribute rule (a price in another source) is read from and written to that source, so the record's own file holds no second copy. |
23
+ | Where-used | `<venture>/knowledge/where-used.json` | For each fact kept once (a price, a product name), every place that repeats it, live or frozen. `schema/check-where-used.mjs` reports drift offline; delivering the change into vendors is the platform's. |
24
+ | Catalog | `catalog.mjs` | One index over every source: address, version, route, title and words. No content. Routes `knowledge/catalog` and `knowledge/search`. |
25
+ | Evals | `suite.mjs`, `eval.mjs` | `suite.mjs` is the contract suite every connector passes, framework or platform. `eval.mjs` runs it on the filesystem connector, then the router over a mapped folder and the catalog. The GitHub connector's evals are `platform/connectors/eval.mjs`. |
26
+
27
+ ## A source map
28
+
29
+ ```json
30
+ {
31
+ "sources": [
32
+ { "id": "team-repo", "connector": "github", "repo": "acme/tandem", "ref": "main", "root": "ventures/tandem" }
33
+ ],
34
+ "records": { "product": "team-repo" }
35
+ }
36
+ ```
37
+
38
+ The `github` connector is a platform connector (`platform/connectors/github.mjs`). With this map, `purpose/product/kettle` reads `ventures/tandem/products/kettle.json` from `acme/tandem` and says which version and commit it came from. A write opens a draft pull request, which a person accepts by merging. Every other record stays in the venture folder. The venture folder always keeps `venture.json`, the provenance log and drafts.
39
+
40
+ ## Adding a connector
41
+
42
+ 1. Decide which side it is on. If it works offline, needs no credentials and names no vendor, it is framework and goes in `knowledge/connectors/<id>.mjs`. Otherwise it is platform and goes in `platform/connectors/<id>.mjs` (EE-041). `node schema/check-boundary.mjs` enforces this.
43
+ 2. Export `connect(options)`, returning an object that meets the contract.
44
+ 3. Add a `connector` to its entry in `knowledge/tooling/integrations.json`: the module, its write modes and its options.
45
+ 4. Run it through `contract()` from `suite.mjs`: against a fake of its API offline, and against the real one when credentials are given.
46
+
47
+ ## Running the evals
48
+
49
+ ```sh
50
+ node knowledge/connectors/eval.mjs # framework
51
+ node platform/connectors/eval.mjs # platform (GitHub, against a fake)
52
+ MENTURA_EVAL_GITHUB_REPO=owner/name GITHUB_TOKEN=... node platform/connectors/eval.mjs # also live
53
+ MENTURA_EVAL_GITHUB_WRITE=1 ... # and open one real draft PR
54
+ ```
@@ -0,0 +1,118 @@
1
+ // The catalog (EE-037): one index over every item in every source of a venture, so a person or an
2
+ // agent can find a record without knowing where it lives. For each item it keeps the address,
3
+ // source, path, version, route and record type (when the item is a record), a title and the set
4
+ // of words in it. It keeps no content: the words are a sorted set, not the text, and reading an
5
+ // item always goes back to its source. A rebuild reads only items whose version changed.
6
+ //
7
+ // node knowledge/connectors/catalog.mjs <venture dir> [--json] build or refresh
8
+ // node knowledge/connectors/catalog.mjs <venture dir> --search <q> [--json] [--live]
9
+ // search the catalog (built first if there is none); --live asks each source instead
10
+ //
11
+ // The catalog is written to <venture>/_catalog/catalog.json. It is derived, so a venture may
12
+ // ignore it in git; the router never routes _ folders.
13
+
14
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
15
+ import { join } from "node:path";
16
+ import { address, isText, words } from "./contract.mjs";
17
+ import { connectAll } from "./index.mjs";
18
+
19
+ export const FILE = "_catalog/catalog.json";
20
+ const MAX_BYTES = 1_000_000;
21
+
22
+ function title(path, content) {
23
+ if (path.endsWith(".json")) {
24
+ try { const j = JSON.parse(content); const t = j && typeof j === "object" && !Array.isArray(j) ? j.name ?? j.title ?? j.statement ?? j.id : null; if (typeof t === "string") return t.slice(0, 160); } catch {}
25
+ }
26
+ const fm = content.match(/^---\n[\s\S]*?\ntitle:\s*["']?(.+?)["']?\n[\s\S]*?\n---/);
27
+ if (fm) return fm[1];
28
+ const h = content.match(/^#\s+(.+)$/m);
29
+ return h ? h[1].trim() : path.split("/").at(-1);
30
+ }
31
+
32
+ // Which route a file in a source belongs to, from the route table (lazy, so the catalog works
33
+ // without the router for a plain folder).
34
+ let table = null;
35
+ async function routeOf(path) {
36
+ if (!table) {
37
+ const { routes, glob } = await import("../../interface/router.mjs");
38
+ table = routes().filter((r) => r.kind === "record" && r.collection !== "list").map((r) => ({ r, res: r.files.map((g) => glob(g)) }));
39
+ }
40
+ for (const { r, res } of table) {
41
+ if (!res.some((re) => re.test(path))) continue;
42
+ if (r.collection !== "item") return { route: r.route, record: r.record };
43
+ for (const g of r.files) {
44
+ if (!g.includes("*")) continue;
45
+ const m = path.match(new RegExp("^" + g.split("*").map((s) => s.replace(/[.+^${}()|[\]\\]/g, "\\$&")).join("([^/]*)") + "$"));
46
+ if (m) return { route: r.route.replace("[id]", m[1]), record: r.record };
47
+ }
48
+ return { route: r.route, record: r.record };
49
+ }
50
+ return { route: null, record: null };
51
+ }
52
+
53
+ export const load = (dir) => existsSync(join(dir, FILE)) ? JSON.parse(readFileSync(join(dir, FILE), "utf8")) : null;
54
+
55
+ /** Builds or refreshes the catalog. Returns it with stats: items, read (items fetched), reused. */
56
+ export async function build(dir, { write = true, sources } = {}) {
57
+ const prior = new Map((load(dir)?.items ?? []).map((x) => [x.address, x]));
58
+ const conns = sources ?? await connectAll(dir);
59
+ const items = [];
60
+ const stats = { sources: conns.length, items: 0, read: 0, reused: 0, skipped: 0, errors: [] };
61
+ for (const { source, connector } of conns) {
62
+ let listed;
63
+ try { listed = await connector.list(); } catch (e) { stats.errors.push(`${source.id}: ${e.message}`); continue; }
64
+ for (const { path, version } of listed) {
65
+ const at = address(source.id, path);
66
+ const old = prior.get(at);
67
+ const where = await routeOf(path);
68
+ if (old && old.version === version) { items.push({ ...old, ...where }); stats.reused++; continue; }
69
+ if (!isText(path)) { items.push({ address: at, source: source.id, path, version, ...where, title: path.split("/").at(-1), terms: words(path) }); stats.skipped++; continue; }
70
+ const got = await connector.read(path);
71
+ stats.read++;
72
+ if (!got || got.content.length > MAX_BYTES || !isText(path, got.content)) { items.push({ address: at, source: source.id, path, version, ...where, title: path.split("/").at(-1), terms: words(path) }); continue; }
73
+ items.push({ address: at, source: source.id, path, version: got.version, ...where, title: title(path, got.content), terms: [...new Set(words(`${path}\n${got.content}`))].sort() });
74
+ }
75
+ }
76
+ stats.items = items.length;
77
+ const catalog = { built_at: new Date().toISOString(), sources: conns.map(({ source }) => ({ id: source.id, connector: source.connector })), items };
78
+ if (write) { mkdirSync(join(dir, "_catalog"), { recursive: true }); writeFileSync(join(dir, FILE), JSON.stringify(catalog) + "\n"); }
79
+ return { catalog, stats };
80
+ }
81
+
82
+ /** Items holding every word of the query, best first: words in the title count double. */
83
+ export function search(catalog, query, { limit = 20 } = {}) {
84
+ const q = words(query);
85
+ if (!q.length) return [];
86
+ return catalog.items.map((x) => {
87
+ const terms = new Set(x.terms), t = words(x.title).join(" ");
88
+ if (!q.every((w) => terms.has(w) || x.terms.some((y) => y.startsWith(w)))) return null;
89
+ return { score: q.reduce((s, w) => s + (terms.has(w) ? 2 : 1) + (t.includes(w) ? 2 : 0), 0), ...x };
90
+ }).filter(Boolean).sort((a, b) => b.score - a.score || a.address.localeCompare(b.address)).slice(0, limit)
91
+ .map(({ terms, ...x }) => x);
92
+ }
93
+
94
+ /** Asks each source's own search instead of the catalog. */
95
+ export async function searchLive(dir, query, { limit = 20 } = {}) {
96
+ const out = [];
97
+ for (const { source, connector } of await connectAll(dir)) {
98
+ try { for (const x of await connector.search(query, { limit })) out.push({ address: address(source.id, x.path), source: source.id, path: x.path, version: x.version, ...(await routeOf(x.path)) }); }
99
+ catch (e) { out.push({ source: source.id, error: e.message }); }
100
+ }
101
+ return out;
102
+ }
103
+
104
+ if (import.meta.url === `file://${process.argv[1]}`) {
105
+ const [dir] = process.argv.slice(2).filter((a, i, all) => !a.startsWith("--") && all[i - 1] !== "--search");
106
+ const i = process.argv.indexOf("--search");
107
+ const json = process.argv.includes("--json");
108
+ if (!dir) { console.error("usage: node knowledge/connectors/catalog.mjs <venture dir> [--search <q>] [--live] [--json]"); process.exit(2); }
109
+ if (i >= 0) {
110
+ const q = process.argv[i + 1] ?? "";
111
+ const hits = process.argv.includes("--live") ? await searchLive(dir, q) : search(load(dir) ?? (await build(dir)).catalog, q);
112
+ console.log(json ? JSON.stringify({ query: q, hits }, null, 2) : hits.map((h) => `${h.address} ${h.title ?? ""}${h.route ? ` (${h.route})` : ""}`).join("\n") || "no items match");
113
+ } else {
114
+ const { stats } = await build(dir);
115
+ console.log(json ? JSON.stringify(stats, null, 2) : `catalog: ${stats.items} items from ${stats.sources} sources (${stats.read} read, ${stats.reused} unchanged)${stats.errors.length ? `\n${stats.errors.join("\n")}` : ""}`);
116
+ if (stats.errors.length) process.exitCode = 1;
117
+ }
118
+ }
@@ -0,0 +1,56 @@
1
+ // The connector contract (EE-037). A connector reaches one source of records (a folder, a GitHub
2
+ // repository, later a database, Notion or Salesforce) and does the same five things for every one:
3
+ //
4
+ // list(prefix) [{ path, version }] for every item under the prefix, sorted by path
5
+ // read(path) { path, content, version, commit?, permalink? }, or null when it is missing
6
+ // version(path) the item's version, or null when it is missing
7
+ // search(query, {limit}) [{ path, version }] for items whose text holds every word of the query
8
+ // write(path, content, {message, title})
9
+ // { path, version, commit?, pull_request? }; how it writes is the
10
+ // connector's `writes`: direct (in place), pull-request (a draft pull
11
+ // request; merging it accepts the write) or none (read-only, write throws)
12
+ //
13
+ // Paths are relative to the source's root and use forward slashes; a path that leaves the root is
14
+ // refused. Content is UTF-8 text. A version is the git blob SHA-1 of the content, whichever
15
+ // connector holds it, so the same content has the same version in every source and a version
16
+ // can be checked without asking the source. An item's address is <source id>://<path>; with its
17
+ // version it is what provenance cites.
18
+
19
+ import { createHash } from "node:crypto";
20
+
21
+ export const METHODS = ["list", "read", "version", "search", "write"];
22
+ export const WRITES = ["direct", "pull-request", "none"];
23
+
24
+ /** The git blob SHA-1 of some content: what `git hash-object` prints. */
25
+ export const blobSha = (content) => {
26
+ const buf = Buffer.isBuffer(content) ? content : Buffer.from(content, "utf8");
27
+ return createHash("sha1").update(`blob ${buf.length}\0`).update(buf).digest("hex");
28
+ };
29
+
30
+ /** A path inside a source, normalised, or an error for one that is empty or leaves the root. */
31
+ export function safePath(p) {
32
+ if (typeof p !== "string" || !p.length || p.includes("\0") || p.includes("\\") || p.startsWith("/")) throw new Error(`${JSON.stringify(p)} is not a path inside the source`);
33
+ const parts = p.split("/").filter((s) => s && s !== ".");
34
+ if (!parts.length || parts.includes("..")) throw new Error(`${p} is not a path inside the source`);
35
+ return parts.join("/");
36
+ }
37
+
38
+ export const address = (sourceId, path) => `${sourceId}://${path}`;
39
+
40
+ /** Every word of the query, lowercased, appears in the text. */
41
+ export const matches = (text, query) => {
42
+ const t = text.toLowerCase();
43
+ return words(query).every((w) => t.includes(w));
44
+ };
45
+ export const words = (s) => String(s).toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((w) => w.length > 1);
46
+
47
+ /** Text a catalog may index: small files that are not binary. */
48
+ export const isText = (path, content) => !/\.(png|jpe?g|gif|webp|ico|pdf|zip|gz|woff2?|ttf|mp4|mov)$/i.test(path) && (content === undefined || !content.includes("\0"));
49
+
50
+ /** Checks an object has the contract's methods and a known `writes`. Returns the problems. */
51
+ export function conformance(c) {
52
+ const problems = METHODS.filter((m) => typeof c?.[m] !== "function").map((m) => `no ${m}()`);
53
+ if (!WRITES.includes(c?.writes)) problems.push(`writes is ${c?.writes}, not one of ${WRITES.join(", ")}`);
54
+ if (typeof c?.id !== "string") problems.push("no id");
55
+ return problems;
56
+ }
@@ -0,0 +1,176 @@
1
+ // Evals for the framework's connectors (EE-037, EE-041). The framework ships one connector, the
2
+ // file system, which works offline and names no vendor; connectors that reach a vendor are
3
+ // platform and have their own evals (platform/connectors/eval.mjs), against the same suite.
4
+ // Three parts:
5
+ //
6
+ // contract the shared suite (suite.mjs) on the filesystem connector, in a plain folder and in
7
+ // a git repository, plus the venture-folder rules
8
+ // router a venture whose source map keeps one record type in another folder: reads come
9
+ // from there with their version, a write lands there and is logged by address,
10
+ // nothing invalid reaches the source, and every other record stays in the venture
11
+ // catalog one index across both sources, and a rebuild reads only what changed
12
+ //
13
+ // node knowledge/connectors/eval.mjs
14
+
15
+ import { spawnSync } from "node:child_process";
16
+ import { cpSync, existsSync, mkdtempSync, readFileSync } from "node:fs";
17
+ import { join } from "node:path";
18
+ import { blobSha } from "./contract.mjs";
19
+ import { contract, FIXTURE, harness, OUTSIDE } from "./suite.mjs";
20
+ import { connect as fsConnect } from "./filesystem.mjs";
21
+ import { build, FILE, load, search } from "./catalog.mjs";
22
+
23
+ const repo = new URL("../..", import.meta.url).pathname;
24
+ const h = harness("connectors");
25
+ const { expect, write, scratch, finish } = h;
26
+
27
+ // The filesystem connector, in a plain folder.
28
+ const fsMake = (git) => async ({ writes = "direct" } = {}) => {
29
+ const d = mkdtempSync(join(scratch, "fs-"));
30
+ write(join(d, "kb"), FIXTURE);
31
+ write(d, OUTSIDE);
32
+ if (git) {
33
+ const g = (...a) => spawnSync("git", ["-C", d, ...a], { encoding: "utf8" });
34
+ g("init", "-q"); g("add", "."); g("-c", "user.name=eval", "-c", "user.email=eval@example.com", "commit", "-qm", "fixture");
35
+ }
36
+ return { c: fsConnect({ id: "folder", root: join(d, "kb"), writes }), dir: d };
37
+ };
38
+ await contract(h, "filesystem (plain folder)", fsMake(false));
39
+ const gitT = await contract(h, "filesystem (git repository)", fsMake(true));
40
+ {
41
+ const fresh = await fsMake(true)();
42
+ const r = await fresh.c.read("notes/beta.md");
43
+ const head = spawnSync("git", ["-C", fresh.dir, "rev-parse", "HEAD"], { encoding: "utf8" }).stdout.trim();
44
+ expect("filesystem (git repository): a committed, unchanged item gives the commit that last touched it", r.commit === head);
45
+ await fresh.c.write("notes/beta.md", "changed\n");
46
+ expect("filesystem (git repository): a changed item gives no commit, since no commit holds that version", !(await fresh.c.read("notes/beta.md")).commit);
47
+ void gitT;
48
+ }
49
+ {
50
+ const d = mkdtempSync(join(scratch, "venture-"));
51
+ write(d, { "venture.json": "{}", "purpose/telos.json": "{}", "_drafts/x.draft": "{}", "studio/venture.json": "{}", "studio/purpose/telos.json": "{}" });
52
+ const v = fsConnect({ root: d, venture: true });
53
+ expect("filesystem (venture folder): listing stops at a nested venture and skips private _ folders", JSON.stringify((await v.list()).map((x) => x.path)) === JSON.stringify(["purpose/telos.json", "venture.json"]));
54
+ }
55
+
56
+ // ---- Router --------------------------------------------------------------------------------------
57
+ console.log("\n# router over a mapped source");
58
+ const ws = join(scratch, "workspace");
59
+ cpSync(join(repo, "schema/examples/workspace"), ws, { recursive: true });
60
+ process.env.MENTURA_WORKSPACE = ws;
61
+ const tandem = join(ws, "ventures/tandem");
62
+ const shared = join(ws, "shared");
63
+ const { call } = await import("../../interface/router.mjs");
64
+ const { entries } = await import("../../interface/provenance.mjs");
65
+ const kettle = { ...Object.values((await call({ venture: "tandem", route: "purpose/product", action: "read" })).result)[0], id: "kettle", name: "Kettle (in the shared folder)" };
66
+ write(shared, { "products/kettle.json": JSON.stringify(kettle, null, 2) + "\n", "notes/launch.md": "# Launch plan\n\nShip the kettle subscription in spring.\n" });
67
+ const map = { sources: [{ id: "shared", connector: "filesystem", root: "../../shared" }], records: { product: "shared" } };
68
+ write(tandem, { "knowledge/sources.json": JSON.stringify(map, null, 2) + "\n" });
69
+ const localProducts = existsSync(join(tandem, "products")) ? spawnSync("ls", [join(tandem, "products")], { encoding: "utf8" }).stdout.trim().split("\n") : [];
70
+
71
+ const list = await call({ venture: "tandem", route: "purpose/product", action: "read" });
72
+ expect("router: a mapped record type is read from its source, not the venture folder", list.ok && JSON.stringify(Object.keys(list.result)) === JSON.stringify(["kettle"]) && localProducts.length > 0, JSON.stringify(list).slice(0, 300));
73
+ const one = await call({ venture: "tandem", route: "purpose/product/kettle", action: "read" });
74
+ expect("router: a read says which source and version it came from", one.result?.name === "Kettle (in the shared folder)" && one.source?.address === "shared://products/kettle.json"
75
+ && one.source.version === blobSha(JSON.stringify(kettle, null, 2) + "\n"), JSON.stringify(one.source));
76
+ const local = await call({ venture: "tandem", route: "purpose/validation/problem", action: "read" });
77
+ expect("router: every record type the map does not name stays in the venture folder", local.ok && local.source?.source === "venture" && local.source.version === blobSha(readFileSync(join(tandem, "purpose/validation/problem.json"))));
78
+ const teapot = { ...kettle, id: "teapot", name: "Teapot" };
79
+ const bad = await call({ venture: "tandem", route: "purpose/product/teapot", action: "write", input: { id: "teapot" } });
80
+ expect("router: a write that does not match the schema never reaches the source", !bad.ok && /does not match/.test(bad.error) && !existsSync(join(shared, "products/teapot.json")));
81
+ const w = await call({ venture: "tandem", route: "purpose/product/teapot", action: "write", input: teapot, actor: "person", via: "cli", provenance: { note: "add the teapot" } });
82
+ expect("router: a write lands in the mapped source, not the venture folder", w.ok && JSON.parse(readFileSync(join(shared, "products/teapot.json"), "utf8")).name === "Teapot" && !existsSync(join(tandem, "products/teapot.json")), JSON.stringify(w));
83
+ const e = entries(tandem).at(-1);
84
+ expect("router: the write is logged by address, with the source's version", e.entity === "shared://products/teapot.json" && e.source?.connector === "filesystem" && e.source.version === blobSha(readFileSync(join(shared, "products/teapot.json"))), JSON.stringify(e));
85
+ const prov = spawnSync("node", [join(repo, "schema/check-provenance.mjs"), ws], { encoding: "utf8" });
86
+ const srcCheck = (d) => spawnSync("node", [join(repo, "schema/check-sources.mjs"), d], { encoding: "utf8" });
87
+ expect("router: check-provenance accepts entries for records in another source", prov.status === 0, prov.stderr);
88
+ expect("router: check-sources accepts the map", srcCheck(ws).status === 0, srcCheck(ws).stderr);
89
+ const valid = spawnSync("python3", ["-I", join(repo, "schema/validate.py"), "--schema", "knowledge-sources.schema.json", "--file", join(tandem, "knowledge/sources.json")], { encoding: "utf8" });
90
+ expect("router: the map matches its schema", valid.status === 0, valid.stdout + valid.stderr);
91
+ write(tandem, { "knowledge/sources.json": JSON.stringify({ sources: [{ id: "team-repo", connector: "notion" }], records: { product: "team-repo", venture: "team-repo" } }) });
92
+ const broken = srcCheck(ws);
93
+ expect("router: check-sources refuses an unknown connector and a record type that must stay in the folder", broken.status === 1 && /no connector notion/.test(broken.stderr) && /venture stays in the venture folder/.test(broken.stderr), broken.stderr);
94
+ const refused = await call({ venture: "tandem", route: "purpose/product", action: "read" });
95
+ expect("router: a read through an unknown connector fails and says which connectors exist", !refused.ok && /no connector notion: .*filesystem/.test(refused.error), refused.error);
96
+ write(tandem, { "knowledge/sources.json": JSON.stringify({ ...map, sources: [{ ...map.sources[0], writes: "none" }] }) });
97
+ const ro = await call({ venture: "tandem", route: "purpose/product/teapot", action: "write", input: teapot, actor: "person" });
98
+ expect("router: a write to a read-only source is refused", !ro.ok && /read-only/.test(ro.error));
99
+ write(tandem, { "knowledge/sources.json": JSON.stringify(map, null, 2) + "\n" });
100
+
101
+ // ---- Catalog -------------------------------------------------------------------------------------
102
+ console.log("\n# catalog");
103
+ const { stats } = await build(tandem);
104
+ const cat = load(tandem);
105
+ expect("catalog: indexes every item in both sources", cat.items.some((x) => x.source === "shared" && x.path === "notes/launch.md") && cat.items.some((x) => x.source === "venture" && x.path === "purpose/validation/problem.json") && stats.errors.length === 0, JSON.stringify(stats));
106
+ expect("catalog: every item has an address and its version", cat.items.every((x) => x.address === `${x.source}://${x.path}` && /^[0-9a-f]{40}$/.test(x.version)));
107
+ expect("catalog: records are tied to their routes, wherever they live", cat.items.find((x) => x.address === "shared://products/kettle.json")?.route === "product/kettle" && cat.items.find((x) => x.path === "purpose/validation/problem.json")?.route === "foundation/problem");
108
+ const s1 = search(cat, "kettle subscription spring");
109
+ expect("catalog: search finds an item in the mapped source", s1[0]?.address === "shared://notes/launch.md" && s1[0].title === "Launch plan", JSON.stringify(s1.slice(0, 2)));
110
+ const research = readFileSync(join(tandem, "knowledge/research/pilot-2026-10.md"), "utf8");
111
+ expect("catalog: search finds an item in the venture folder", search(cat, "pilot").some((x) => x.address === "venture://knowledge/research/pilot-2026-10.md"));
112
+ const raw = readFileSync(join(tandem, FILE), "utf8");
113
+ const phrase = research.split("\n").find((l) => l.split(" ").length > 6)?.slice(0, 40);
114
+ expect("catalog: keeps no content (no sentence of an item appears in it)", phrase && !raw.includes(phrase) && !raw.includes("Ship the kettle subscription"), phrase);
115
+ const again = (await build(tandem)).stats;
116
+ expect("catalog: a rebuild with nothing changed reads nothing", again.read === 0 && again.reused === stats.items, JSON.stringify(again));
117
+ write(shared, { "notes/later.md": "# Later\n\nAdded after the first build.\n" });
118
+ const changed = (await build(tandem)).stats;
119
+ expect("catalog: after a change, a rebuild reads only the new item", changed.read === 1 && load(tandem).items.some((x) => x.address === "shared://notes/later.md"), JSON.stringify(changed));
120
+
121
+ // ---- Matrix (EE-042) ------------------------------------------------------------------------------
122
+ console.log("\n# source-of-truth matrix");
123
+ const { resolve: resolveRule } = await import("./resolve.mjs");
124
+ const { sourceFor } = await import("./index.mjs");
125
+ const rule = (o) => ({ record: "product", attribute: "*", phase: "*", product: "*", account: "*", actor: "*", owner: "founder", ...o });
126
+ const matrix = { sources: map.sources, records: {}, rules: [
127
+ rule({ system_of_record: "venture" }),
128
+ rule({ phase: "validation", system_of_record: "shared" }),
129
+ rule({ product: "kettle", phase: "validation", system_of_record: "venture", note: "the kettle stays in the folder" }),
130
+ rule({ attribute: "price", system_of_record: "shared" }),
131
+ ] };
132
+ expect("matrix: the most specific rule wins", resolveRule(matrix, { record: "product", phase: "validation", product: "kettle" }).rule.note === "the kettle stays in the folder");
133
+ expect("matrix: a rule narrowed to a phase holds only in that phase", resolveRule(matrix, { record: "product", phase: "validation" }).rule.system_of_record === "shared" && resolveRule(matrix, { record: "product", phase: "scale" }).rule.system_of_record === "venture");
134
+ expect("matrix: a field the query leaves out matches only \"*\"", resolveRule(matrix, { record: "product" }).rule.system_of_record === "venture");
135
+ expect("matrix: an attribute rule answers for that attribute, not for whole-record reads", resolveRule(matrix, { record: "product", attribute: "price" }).rule.attribute === "price" && resolveRule(matrix, { record: "product" }).rule.attribute === "*");
136
+ expect("matrix: a record type no rule names falls back to the records shorthand, then to nothing", resolveRule({ records: { account: "shared" } }, { record: "account" }).rule.system_of_record === "shared" && resolveRule(matrix, { record: "telos" }) === null);
137
+ expect("matrix: two rules that hold at once at the same specificity tie", resolveRule({ rules: [rule({ phase: "validation", system_of_record: "a" }), rule({ actor: "agent", system_of_record: "b" })] }, { record: "product", phase: "validation", actor: "agent" }).ties.length === 2);
138
+ write(tandem, { "knowledge/sources.json": JSON.stringify(matrix, null, 2) + "\n" });
139
+ expect("matrix: sourceFor reads the venture's phase from venture.json (tandem is in validation)", sourceFor(tandem, "product").id === "shared" && sourceFor(tandem, "product", { product: "kettle" }).id === "venture");
140
+ const viaRouter = await call({ venture: "tandem", route: "purpose/product/teapot", action: "read" });
141
+ const kettleWrite = await call({ venture: "tandem", route: "purpose/product/kettle", action: "write", input: { ...kettle, name: "Kettle (kept in the folder)" }, actor: "person" });
142
+ expect("matrix: the router routes each read and write by its context", viaRouter.source?.source === "shared" && kettleWrite.ok && existsSync(join(tandem, "products/kettle.json")) && JSON.parse(readFileSync(join(shared, "products/kettle.json"), "utf8")).name === "Kettle (in the shared folder)", JSON.stringify([viaRouter.source, kettleWrite]));
143
+ expect("matrix: check-sources accepts the matrix", srcCheck(ws).status === 0, srcCheck(ws).stderr);
144
+ write(tandem, { "knowledge/sources.json": JSON.stringify({ ...matrix, rules: [...matrix.rules, rule({ actor: "agent", system_of_record: "venture" })] }, null, 2) + "\n" });
145
+ const tie = srcCheck(ws);
146
+ expect("matrix: check-sources refuses two rules that can tie", tie.status === 1 && /tie at the same specificity/.test(tie.stderr), tie.stderr);
147
+ const tieRead = await call({ venture: "tandem", route: "purpose/product", action: "read", actor: "agent" });
148
+ expect("matrix: a read that two rules tie on fails and says so", !tieRead.ok && /tie at the same specificity/.test(tieRead.error), tieRead.error);
149
+
150
+ // Attributes mastered elsewhere: the product's description lives in the shared folder, the rest
151
+ // of the product in the venture folder.
152
+ const byAttr = { sources: map.sources, records: {}, rules: [rule({ system_of_record: "venture" }), rule({ attribute: "description", system_of_record: "shared" })] };
153
+ write(tandem, { "knowledge/sources.json": JSON.stringify(byAttr, null, 2) + "\n" });
154
+ write(shared, { "products/shop-app.json": JSON.stringify({ description: "Mastered in the shared folder" }, null, 2) + "\n" });
155
+ const shop = await call({ venture: "tandem", route: "purpose/product/shop-app", action: "read" });
156
+ expect("attributes: a read fills each attribute from its own system of record and cites it", shop.result?.description === "Mastered in the shared folder" && shop.source?.source === "venture" && shop.source.attributes?.description?.address === "shared://products/shop-app.json", JSON.stringify(shop.source));
157
+ const shopList = await call({ venture: "tandem", route: "purpose/product", action: "read" });
158
+ expect("attributes: list reads overlay too", shopList.result?.["shop-app"]?.description === "Mastered in the shared folder");
159
+ write(shared, { "products/kettle.json": "{}\n" });
160
+ const noFrag = await call({ venture: "tandem", route: "purpose/product/kettle", action: "read" });
161
+ expect("attributes: a source holding nothing for the attribute leaves the record's own value and says so", noFrag.result?.name === "Kettle (kept in the folder)" && noFrag.result?.description === kettle.description && noFrag.source.attributes?.description?.missing === true, JSON.stringify(noFrag.source));
162
+ const shopWrite = await call({ venture: "tandem", route: "purpose/product/shop-app", action: "write", input: { ...shop.result, description: "Changed once, in one place" }, actor: "person", provenance: { note: "new description" } });
163
+ const own = JSON.parse(readFileSync(join(tandem, "products/shop-app.json"), "utf8"));
164
+ const frag = JSON.parse(readFileSync(join(shared, "products/shop-app.json"), "utf8"));
165
+ expect("attributes: a write sends the attribute to its system of record and leaves it out of the record's own file", shopWrite.ok && frag.description === "Changed once, in one place" && !("description" in own) && shopWrite.result.attributes?.[0]?.written === "shared://products/shop-app.json", JSON.stringify(shopWrite));
166
+ expect("attributes: the attribute write is logged by address and attribute", entries(tandem).some((x) => x.entity === "shared://products/shop-app.json#description"));
167
+ const after = await call({ venture: "tandem", route: "purpose/product/shop-app", action: "read" });
168
+ expect("attributes: the next read shows the one value", after.result?.description === "Changed once, in one place");
169
+ const badAttr = await call({ venture: "tandem", route: "purpose/product/shop-app", action: "write", input: { id: "shop-app" }, actor: "person" });
170
+ expect("attributes: a record that fails its schema writes nothing anywhere", !badAttr.ok && JSON.parse(readFileSync(join(shared, "products/shop-app.json"), "utf8")).description === "Changed once, in one place");
171
+ write(tandem, { "knowledge/sources.json": JSON.stringify({ ...byAttr, sources: [{ ...map.sources[0], writes: "none" }] }, null, 2) + "\n" });
172
+ const roAttr = await call({ venture: "tandem", route: "purpose/product/shop-app", action: "write", input: after.result, actor: "person" });
173
+ expect("attributes: a write whose attribute is mastered in a read-only source is refused before anything lands", !roAttr.ok && /read-only/.test(roAttr.error));
174
+ write(tandem, { "knowledge/sources.json": JSON.stringify(map, null, 2) + "\n" });
175
+
176
+ finish();