fde-framework 0.1.4__tar.gz → 0.1.6__tar.gz

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 (357) hide show
  1. {fde_framework-0.1.4 → fde_framework-0.1.6}/CHANGELOG.md +59 -0
  2. {fde_framework-0.1.4 → fde_framework-0.1.6}/PKG-INFO +18 -16
  3. {fde_framework-0.1.4 → fde_framework-0.1.6}/README.md +17 -15
  4. {fde_framework-0.1.4 → fde_framework-0.1.6}/examples/invoice-extraction/README.md +4 -3
  5. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/stacks/ollama.md +6 -0
  6. fde_framework-0.1.6/framework/stacks/vllm.md +21 -0
  7. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/retrieval/graph-expanded-retrieval.pgvector.py.j2 +6 -1
  8. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/retrieval/graph-expanded-retrieval.plain.py.j2 +5 -2
  9. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/retrieval/graph-expanded-retrieval.qdrant.py.j2 +8 -2
  10. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/retrieval/graph-retrieval.qdrant.py.j2 +2 -2
  11. {fde_framework-0.1.4 → fde_framework-0.1.6}/pyproject.toml +1 -1
  12. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/architect.py +10 -1
  13. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/cli.py +39 -3
  14. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/decide.py +16 -3
  15. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/emit.py +92 -27
  16. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/gates.py +4 -1
  17. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/ops.py +72 -20
  18. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/scan.py +23 -1
  19. fde_framework-0.1.6/tests/test_audit_fixes.py +237 -0
  20. fde_framework-0.1.6/tests/test_build_receipt.py +101 -0
  21. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_retrieval_eval.py +103 -0
  22. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_scan.py +37 -0
  23. fde_framework-0.1.4/framework/stacks/vllm.md +0 -9
  24. {fde_framework-0.1.4 → fde_framework-0.1.6}/.gitignore +0 -0
  25. {fde_framework-0.1.4 → fde_framework-0.1.6}/LICENSE +0 -0
  26. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/ansible-playbook.md +0 -0
  27. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/assisted-deterministic.md +0 -0
  28. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/audit-only.md +0 -0
  29. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/boundary-and-audit.md +0 -0
  30. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/cascade.md +0 -0
  31. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/classical-ml.md +0 -0
  32. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/compose.md +0 -0
  33. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/decision-log.md +0 -0
  34. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/deterministic-masking.md +0 -0
  35. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/deterministic.md +0 -0
  36. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/direct-call.md +0 -0
  37. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/episodic-store.md +0 -0
  38. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/explainability-record.md +0 -0
  39. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/field-match.md +0 -0
  40. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/finetune.md +0 -0
  41. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/fixed-sequence.md +0 -0
  42. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/gitops.md +0 -0
  43. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/governed-tools.md +0 -0
  44. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/graph-expanded-retrieval.md +0 -0
  45. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/graph-retrieval.md +0 -0
  46. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/hybrid-search.md +0 -0
  47. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/judged.md +0 -0
  48. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/keyword-search.md +0 -0
  49. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/kubernetes-manifests.md +0 -0
  50. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/labelled-metrics.md +0 -0
  51. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/llm-extraction.md +0 -0
  52. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/llm-scrubbing.md +0 -0
  53. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/llm.md +0 -0
  54. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/local-embedding.md +0 -0
  55. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/managed-api.md +0 -0
  56. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/managed-embedding.md +0 -0
  57. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/manual-runbook.md +0 -0
  58. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/model-planner.md +0 -0
  59. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/ocr-pipeline.md +0 -0
  60. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/optimisation-reasoning.md +0 -0
  61. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/optimisation.md +0 -0
  62. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/passthrough.md +0 -0
  63. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/reranked-retrieval.md +0 -0
  64. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/role-scoped-authority.md +0 -0
  65. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/segmentation.md +0 -0
  66. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/self-hosted.md +0 -0
  67. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/serverless-gpu.md +0 -0
  68. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/speech-transcription.md +0 -0
  69. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/structured-logs.md +0 -0
  70. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/systemd-unit.md +0 -0
  71. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/terraform-module.md +0 -0
  72. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/text-extraction.md +0 -0
  73. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/traced.md +0 -0
  74. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/vector-search.md +0 -0
  75. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/video-ingestion.md +0 -0
  76. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/windowed-ingestion.md +0 -0
  77. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/approaches/working-state.md +0 -0
  78. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/cases/churn-scoring.md +0 -0
  79. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/cases/route-planning.md +0 -0
  80. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/cases/structured-extraction.md +0 -0
  81. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/cases/studio-style.md +0 -0
  82. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/components/accountability.md +0 -0
  83. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/components/deployment.md +0 -0
  84. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/components/embedding.md +0 -0
  85. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/components/evaluation.md +0 -0
  86. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/components/governance.md +0 -0
  87. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/components/integration.md +0 -0
  88. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/components/memory.md +0 -0
  89. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/components/observability.md +0 -0
  90. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/components/perception.md +0 -0
  91. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/components/planning.md +0 -0
  92. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/components/provisioning.md +0 -0
  93. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/components/reasoning.md +0 -0
  94. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/components/redaction.md +0 -0
  95. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/components/representation.md +0 -0
  96. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/components/retrieval.md +0 -0
  97. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/components/serving.md +0 -0
  98. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/accelerator.md +0 -0
  99. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/access_model.md +0 -0
  100. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/arrival_rate.md +0 -0
  101. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/availability_target.md +0 -0
  102. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/cheap_path_coverage.md +0 -0
  103. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/confidence_calibrated.md +0 -0
  104. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/container_competence.md +0 -0
  105. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/corpus_churn.md +0 -0
  106. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/corpus_size.md +0 -0
  107. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/data_residency.md +0 -0
  108. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/environment_lifetime.md +0 -0
  109. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/existing_cluster.md +0 -0
  110. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/existing_iac_tool.md +0 -0
  111. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/external_systems.md +0 -0
  112. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/hosting.md +0 -0
  113. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/human_waiting.md +0 -0
  114. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/input_format.md +0 -0
  115. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/interpretability_required.md +0 -0
  116. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/labelled_count.md +0 -0
  117. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/latency_budget_ms.md +0 -0
  118. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/licence_posture.md +0 -0
  119. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/operates_after_handover.md +0 -0
  120. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/output_shape.md +0 -0
  121. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/provisioning_api.md +0 -0
  122. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/query_pattern.md +0 -0
  123. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/recall_span.md +0 -0
  124. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/dimensions/sensitivity_present.md +0 -0
  125. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/interfaces/Generator.md +0 -0
  126. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/interfaces/Guard.md +0 -0
  127. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/interfaces/Mapper.md +0 -0
  128. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/interfaces/ModelServer.md +0 -0
  129. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/interfaces/Parser.md +0 -0
  130. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/interfaces/Planner.md +0 -0
  131. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/interfaces/Retriever.md +0 -0
  132. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/interfaces/Scorer.md +0 -0
  133. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/interfaces/Store.md +0 -0
  134. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/interfaces/ToolBoundary.md +0 -0
  135. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/interfaces/Tracer.md +0 -0
  136. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/locales/eu-gdpr.md +0 -0
  137. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/locales/in-dpdp.md +0 -0
  138. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/ansible-playbook.md +0 -0
  139. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/assisted-deterministic.md +0 -0
  140. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/audit-only.md +0 -0
  141. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/boundary-and-audit.md +0 -0
  142. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/cascade-reasoning.md +0 -0
  143. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/cascade-representation.md +0 -0
  144. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/cascade-retrieval.md +0 -0
  145. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/classical-ml-reasoning.md +0 -0
  146. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/classical-ml.md +0 -0
  147. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/compose.md +0 -0
  148. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/decision-log.md +0 -0
  149. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/deterministic-masking.md +0 -0
  150. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/deterministic.md +0 -0
  151. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/direct-call.md +0 -0
  152. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/episodic-store.md +0 -0
  153. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/explainability-record.md +0 -0
  154. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/field-match.md +0 -0
  155. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/finetune-representation.md +0 -0
  156. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/finetune.md +0 -0
  157. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/fixed-sequence.md +0 -0
  158. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/gitops.md +0 -0
  159. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/governed-tools.md +0 -0
  160. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/graph-expanded-retrieval.md +0 -0
  161. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/graph-retrieval.md +0 -0
  162. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/hybrid-search.md +0 -0
  163. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/judged.md +0 -0
  164. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/keyword-search.md +0 -0
  165. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/kubernetes-manifests.md +0 -0
  166. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/labelled-metrics.md +0 -0
  167. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/llm-representation.md +0 -0
  168. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/llm-scrubbing.md +0 -0
  169. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/llm.md +0 -0
  170. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/local-embedding.md +0 -0
  171. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/managed-api.md +0 -0
  172. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/managed-embedding.md +0 -0
  173. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/manual-runbook.md +0 -0
  174. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/model-planner.md +0 -0
  175. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/ocr-pipeline.md +0 -0
  176. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/optimisation-reasoning.md +0 -0
  177. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/optimisation.md +0 -0
  178. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/passthrough.md +0 -0
  179. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/reranked-retrieval.md +0 -0
  180. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/role-scoped-authority.md +0 -0
  181. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/segmentation.md +0 -0
  182. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/self-hosted.md +0 -0
  183. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/serverless-gpu.md +0 -0
  184. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/speech-transcription.md +0 -0
  185. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/structured-logs.md +0 -0
  186. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/systemd-unit.md +0 -0
  187. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/terraform-module.md +0 -0
  188. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/text-extraction.md +0 -0
  189. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/traced.md +0 -0
  190. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/vector-search.md +0 -0
  191. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/video-ingestion.md +0 -0
  192. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/windowed-ingestion.md +0 -0
  193. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/patterns/working-state.md +0 -0
  194. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/stacks/langgraph.md +0 -0
  195. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/stacks/llamaindex.md +0 -0
  196. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/stacks/local-judge.md +0 -0
  197. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/stacks/mcp.md +0 -0
  198. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/stacks/openai-judge.md +0 -0
  199. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/stacks/opentelemetry.md +0 -0
  200. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/stacks/ortools.md +0 -0
  201. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/stacks/pgvector.md +0 -0
  202. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/stacks/plain-python.md +0 -0
  203. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/stacks/qdrant.md +0 -0
  204. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/stacks/tesseract.md +0 -0
  205. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/stacks/whisper.md +0 -0
  206. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/stacks/xgboost.md +0 -0
  207. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/accountability/decision-log.plain.py.j2 +0 -0
  208. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/accountability/explainability-record.plain.py.j2 +0 -0
  209. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/deployment/compose.plain.py.j2 +0 -0
  210. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/deployment/kubernetes-manifests.plain.py.j2 +0 -0
  211. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/deployment/systemd-unit.plain.py.j2 +0 -0
  212. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/embedding/local-embedding.plain.py.j2 +0 -0
  213. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/embedding/managed-embedding.plain.py.j2 +0 -0
  214. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/evaluation/field-match.plain.py.j2 +0 -0
  215. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/evaluation/judged.local-judge.py.j2 +0 -0
  216. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/evaluation/judged.openai-judge.py.j2 +0 -0
  217. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/evaluation/judged.plain.py.j2 +0 -0
  218. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/evaluation/labelled-metrics.plain.py.j2 +0 -0
  219. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/evaluation/labelled-metrics.xgboost.py.j2 +0 -0
  220. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/governance/audit-only.plain.py.j2 +0 -0
  221. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/governance/boundary-and-audit.plain.py.j2 +0 -0
  222. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/governance/role-scoped-authority.plain.py.j2 +0 -0
  223. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/integration/direct-call.plain.py.j2 +0 -0
  224. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/integration/governed-tools.mcp.py.j2 +0 -0
  225. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/integration/governed-tools.plain.py.j2 +0 -0
  226. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/memory/episodic-store.pgvector.py.j2 +0 -0
  227. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/memory/episodic-store.plain.py.j2 +0 -0
  228. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/memory/working-state.plain.py.j2 +0 -0
  229. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/observability/structured-logs.plain.py.j2 +0 -0
  230. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/observability/traced.opentelemetry.py.j2 +0 -0
  231. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/observability/traced.plain.py.j2 +0 -0
  232. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/perception/ocr-pipeline.plain.py.j2 +0 -0
  233. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/perception/ocr-pipeline.tesseract.py.j2 +0 -0
  234. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/perception/passthrough.plain.py.j2 +0 -0
  235. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/perception/speech-transcription.plain.py.j2 +0 -0
  236. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/perception/speech-transcription.whisper.py.j2 +0 -0
  237. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/perception/text-extraction.plain.py.j2 +0 -0
  238. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/perception/video-ingestion.plain.py.j2 +0 -0
  239. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/perception/windowed-ingestion.plain.py.j2 +0 -0
  240. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/planning/fixed-sequence.plain.py.j2 +0 -0
  241. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/planning/model-planner.langgraph.py.j2 +0 -0
  242. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/planning/model-planner.plain.py.j2 +0 -0
  243. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/planning/optimisation.ortools.py.j2 +0 -0
  244. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/planning/optimisation.plain.py.j2 +0 -0
  245. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/provisioning/ansible-playbook.plain.py.j2 +0 -0
  246. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/provisioning/gitops.plain.py.j2 +0 -0
  247. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/provisioning/manual-runbook.plain.py.j2 +0 -0
  248. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/provisioning/terraform-module.plain.py.j2 +0 -0
  249. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/reasoning/cascade.plain.py.j2 +0 -0
  250. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/reasoning/classical-ml.plain.py.j2 +0 -0
  251. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/reasoning/classical-ml.xgboost.py.j2 +0 -0
  252. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/reasoning/finetune.plain.py.j2 +0 -0
  253. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/reasoning/llm.plain.py.j2 +0 -0
  254. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/reasoning/optimisation.ortools.py.j2 +0 -0
  255. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/reasoning/optimisation.plain.py.j2 +0 -0
  256. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/redaction/deterministic-masking.plain.py.j2 +0 -0
  257. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/redaction/llm-scrubbing.plain.py.j2 +0 -0
  258. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/representation/assisted.plain.py.j2 +0 -0
  259. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/representation/cascade.plain.py.j2 +0 -0
  260. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/representation/classical-ml.plain.py.j2 +0 -0
  261. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/representation/classical-ml.xgboost.py.j2 +0 -0
  262. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/representation/deterministic.plain.py.j2 +0 -0
  263. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/representation/finetune.plain.py.j2 +0 -0
  264. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/representation/llm.plain.py.j2 +0 -0
  265. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/representation/segmentation.plain.py.j2 +0 -0
  266. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/retrieval/cascade.plain.py.j2 +0 -0
  267. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/retrieval/graph-retrieval.pgvector.py.j2 +0 -0
  268. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/retrieval/graph-retrieval.plain.py.j2 +0 -0
  269. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/retrieval/hybrid-search.plain.py.j2 +0 -0
  270. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/retrieval/keyword-search.plain.py.j2 +0 -0
  271. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/retrieval/reranked-retrieval.plain.py.j2 +0 -0
  272. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/retrieval/vector-search.llamaindex.py.j2 +0 -0
  273. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/retrieval/vector-search.pgvector.py.j2 +0 -0
  274. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/retrieval/vector-search.plain.py.j2 +0 -0
  275. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/retrieval/vector-search.qdrant.py.j2 +0 -0
  276. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/serving/managed-api.plain.py.j2 +0 -0
  277. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/serving/self-hosted.ollama.py.j2 +0 -0
  278. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/serving/self-hosted.plain.py.j2 +0 -0
  279. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/serving/self-hosted.vllm.py.j2 +0 -0
  280. {fde_framework-0.1.4 → fde_framework-0.1.6}/framework/templates/serving/serverless-gpu.plain.py.j2 +0 -0
  281. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/__init__.py +0 -0
  282. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/costing.py +0 -0
  283. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/decompose.py +0 -0
  284. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/deploy.py +0 -0
  285. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/evolution.py +0 -0
  286. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/factlog.py +0 -0
  287. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/graph.py +0 -0
  288. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/implement.py +0 -0
  289. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/intake/__init__.py +0 -0
  290. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/intake/answers.py +0 -0
  291. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/intake/documents.py +0 -0
  292. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/intake/interview.py +0 -0
  293. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/intake/llm_reader.py +0 -0
  294. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/intake/prose.py +0 -0
  295. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/intake/samples.py +0 -0
  296. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/models/__init__.py +0 -0
  297. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/models/base.py +0 -0
  298. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/models/fact.py +0 -0
  299. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/models/profile.py +0 -0
  300. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/models/respondent.py +0 -0
  301. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/models/schema.py +0 -0
  302. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/moves.py +0 -0
  303. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/predicate.py +0 -0
  304. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/realization.py +0 -0
  305. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/registry.py +0 -0
  306. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/space.py +0 -0
  307. {fde_framework-0.1.4 → fde_framework-0.1.6}/src/fde/workflow.py +0 -0
  308. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/__init__.py +0 -0
  309. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/conftest.py +0 -0
  310. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/models/__init__.py +0 -0
  311. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/models/test_profile.py +0 -0
  312. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_access_and_sensitivity.py +0 -0
  313. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_answers.py +0 -0
  314. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_cascade.py +0 -0
  315. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_cli_engagement.py +0 -0
  316. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_cli_evolution.py +0 -0
  317. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_cli_gates.py +0 -0
  318. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_cli_intake.py +0 -0
  319. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_cli_kb.py +0 -0
  320. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_cli_robustness.py +0 -0
  321. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_cli_samples.py +0 -0
  322. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_costing.py +0 -0
  323. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_coverage.py +0 -0
  324. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_dead_zones.py +0 -0
  325. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_decide.py +0 -0
  326. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_decompose.py +0 -0
  327. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_deploy.py +0 -0
  328. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_documents.py +0 -0
  329. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_embedding.py +0 -0
  330. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_emit.py +0 -0
  331. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_evolution.py +0 -0
  332. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_example_transcript.py +0 -0
  333. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_factlog.py +0 -0
  334. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_gates.py +0 -0
  335. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_implement.py +0 -0
  336. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_interview.py +0 -0
  337. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_llm_reader.py +0 -0
  338. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_locales.py +0 -0
  339. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_memory_retrieval.py +0 -0
  340. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_more_templates.py +0 -0
  341. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_moves.py +0 -0
  342. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_multimodal.py +0 -0
  343. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_ops.py +0 -0
  344. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_package.py +0 -0
  345. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_prose.py +0 -0
  346. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_realization.py +0 -0
  347. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_registry.py +0 -0
  348. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_remaining.py +0 -0
  349. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_reversibility.py +0 -0
  350. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_samples.py +0 -0
  351. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_sanitisation.py +0 -0
  352. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_schema.py +0 -0
  353. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_serving.py +0 -0
  354. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_shipped_registry.py +0 -0
  355. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_space.py +0 -0
  356. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_stack_swap.py +0 -0
  357. {fde_framework-0.1.4 → fde_framework-0.1.6}/tests/test_templates.py +0 -0
@@ -5,6 +5,65 @@ the project is pre-release, so everything sits under 0.1.0 until the first tag.
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.1.6] — 2026-09-10
9
+
10
+ Found by the first full demonstration engagement — 626 real scanned
11
+ receipts (SROIE) run through the complete lifecycle on the published
12
+ wheel:
13
+
14
+ - `fde build <bare-name>` resolved the engagement but read the sample
15
+ pairs from the raw argument's path, silently emitting a project with an
16
+ empty golden set while sixty verified pairs sat on the engagement. The
17
+ harness's empty-exam refusal caught it at runtime; now the path is
18
+ resolved once, and the build receipt prints its own exam counts
19
+ ("evals: 42 golden, …") with a pointed warning when pairs exist but
20
+ none reached the golden set.
21
+ - The local-engine choice is a concurrency decision, not only a hardware
22
+ one: `fde scan <engagement>` now reads the recorded arrival rate, and
23
+ past ~1000 requests/day (where the gap between requests drops under a
24
+ generation's length) it names the sequential-queue ceiling and the
25
+ continuous-batching answer. The vllm and ollama stack entries carry the
26
+ doctrine: paged KV admits more concurrent sequences, the scheduler
27
+ retires requests mid-batch, and the crossover is measured, never guessed.
28
+
29
+ ## [0.1.5] — 2026-09-10
30
+
31
+ Three independent fresh-eyes auditors ran against 0.1.4 the day it shipped
32
+ — an adversarial code review, a stranger following only the public docs,
33
+ and a claims audit. Everything they caught, in one release:
34
+
35
+ - The retrieval eval now grades every realization shape the registry can
36
+ emit: a wired module-level instance wins over a fresh construction (a
37
+ working deployment no longer scores 0% with the index blamed), class
38
+ `run(query, top_k=…)` shapes are called correctly, and path-contract
39
+ graph-retrieval is excluded from the recall gate it could never pass.
40
+ Recall math hardened: duplicate relevant ids no longer inflate, wrong-
41
+ shaped results are errors rather than silent zeros diluting a green CI.
42
+ - `graph-retrieval.qdrant`'s expansion loop yielded the wrong variable
43
+ since v0.1.3 — the graph walk was dead code and the approach silently
44
+ degraded to vector search. Fixed and pinned by executing the template.
45
+ The graph-expanded variant survives adjacency entries for deleted
46
+ documents (the continuous-churn case it exists for) and all three
47
+ variants cap expansion breadth, not just depth.
48
+ - The emitted `ops/slo.md` now shows the captured baseline as the numbers
49
+ to beat — it said "Not captured" over a recorded baseline, contradicting
50
+ its own acceptance protocol. `evals/acceptance.md` stops citing a named
51
+ eval owner when the client_readiness gate was waived, and stops citing
52
+ "those 0" cases over an empty golden set.
53
+ - The architecture fingerprint now includes the topology: two engagements
54
+ building byte-different projects (different boundaries) no longer share
55
+ a fingerprint.
56
+ - The interview no longer re-asks a multi-valued question it just
57
+ accepted; the offline_evaluability remedy names its clearing command;
58
+ a rejection whose applies-condition references an unanswered dimension
59
+ now names the question that could reverse it.
60
+ - Docs truth pass: the quickstart names all three gate steps before the
61
+ passing build; the emitted-project tree shows `ops/diagnosis.md` and
62
+ `evals/retrieval.py`; kb commands documented registry-free as they ship;
63
+ README nav links absolute so they resolve on PyPI; ARCHITECTURE.md
64
+ diagram says seven gates; CITATION.cff current; CI checks out full
65
+ history so the sanitisation scan covers what the README says it covers.
66
+
8
67
  ## [0.1.4] — 2026-09-09
9
68
 
10
69
  The measurement release: claims the framework already made, turned into
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: fde-framework
3
- Version: 0.1.4
3
+ Version: 0.1.6
4
4
  Summary: A framework for Forward Deployed Engineers: from problem statement to a deployable AI project, every decision traced to a fact.
5
5
  Project-URL: Homepage, https://github.com/atulkapoor/fde-framework
6
6
  Project-URL: Repository, https://github.com/atulkapoor/fde-framework
@@ -53,9 +53,9 @@ Description-Content-Type: text/markdown
53
53
  <p>
54
54
  <a href="#install">Install</a> ·
55
55
  <a href="#try-it">Quickstart</a> ·
56
- <a href="ARCHITECTURE.md">Architecture</a> ·
57
- <a href="examples/invoice-extraction/">Worked example</a> ·
58
- <a href="CONTRIBUTING.md">Contributing</a> ·
56
+ <a href="https://github.com/atulkapoor/fde-framework/blob/main/ARCHITECTURE.md">Architecture</a> ·
57
+ <a href="https://github.com/atulkapoor/fde-framework/tree/main/examples/invoice-extraction">Worked example</a> ·
58
+ <a href="https://github.com/atulkapoor/fde-framework/blob/main/CONTRIBUTING.md">Contributing</a> ·
59
59
  <a href="https://pypi.org/project/fde-framework/">PyPI</a> ·
60
60
  <a href="https://atulkapoor.github.io/fde-framework/">Website</a>
61
61
  </p>
@@ -75,9 +75,9 @@ including on-premise, inside a customer VPC, or fully air-gapped.
75
75
  pip install fde-framework
76
76
  fde start acme --statement "Extract fields from supplier invoices."
77
77
  fde ask acme --role admin # role-scoped discovery interview
78
- fde architect acme # the design, with cited rationale
78
+ fde architect acme # topology + chosen approaches (rationale lands in ARCHITECTURE.md)
79
79
  fde build acme --out project # refuses: seven gates guard the build
80
- # ...verify data access, capture the baseline (each gate prints its remedy), then:
80
+ # ...verify data access, capture the baseline, name the eval owner (each gate prints its remedy), then:
81
81
  fde build acme --out project # code + evals + deploy assets + runbook
82
82
  ```
83
83
 
@@ -131,9 +131,9 @@ pairs, role-scoped interview, hardware scan) → fact log with provenance →
131
131
  permutation space → seven gates → decide → architect → build (code, evals,
132
132
  deploy and ops assets, `RISKS.md`, `COMPLIANCE.md`) → retro and case
133
133
  capture. Overrides are honoured on the next run, trigger observations feed
134
- calibration, and a reviewed case can enter the corpus. over 800 tests; three
135
- adversarial review rounds (108 findings, each fixed and pinned as a
136
- regression test); CI gates on the suite, lint, and a sanitisation scan of
134
+ calibration, and a reviewed case can enter the corpus. over 870 tests; four
135
+ fresh-eyes review rounds (142 findings, each resolved and the fixes pinned
136
+ as regression tests); CI gates on the suite, lint, and a sanitisation scan of
137
137
  the tree *and its history*; the evidence corpus is anchored to publicly
138
138
  documented production deployments; every decision is reproducible from its
139
139
  inputs.
@@ -265,8 +265,8 @@ fde scan engagements/acme # what this hardware runs
265
265
  fde cost --requests-per-day 500000 --model-b 70 # dated fleet sizing
266
266
 
267
267
  fde kb validate # parse and cross-link the registry
268
- fde kb gaps --root framework # what the corpus is missing
269
- fde kb sweep --root framework # profiles no approach can serve
268
+ fde kb gaps # what the corpus is missing
269
+ fde kb sweep # profiles no approach can serve
270
270
  .venv/bin/pytest -q
271
271
  ```
272
272
 
@@ -290,10 +290,11 @@ project/
290
290
  │ └── llm.py # the one model touchpoint — when a decision needs a model (boundary-gated)
291
291
  ├── evals/ # golden / edge / adversarial sets from the client's own pairs
292
292
  │ ├── harness.py # fails CI until implemented; judge-based when the evaluation decided judged
293
+ │ ├── retrieval.py # recall@10/50 of the retrieval layer alone — when retrieval answers ranked queries
293
294
  │ ├── acceptance.md # blind UAT protocol for the client's own judges
294
295
  │ └── load.py # p95 against the stated budget (when one was stated)
295
296
  ├── deploy/ # the substrate that was earned + TEARDOWN.md for all of it
296
- ├── ops/ # runbook keyed to the failure taxonomy, SLOs, rollback
297
+ ├── ops/ # runbook keyed to the failure taxonomy, diagnosis walk, SLOs, rollback
297
298
  ├── ARCHITECTURE.md # scope read-out, decisions, tools & alternatives, agent posture
298
299
  ├── RISKS.md # every waived gate and overridden recommendation
299
300
  └── COMPLIANCE.md # jurisdiction obligations, when a locale pack was applied
@@ -327,7 +328,7 @@ fde security-review acme --note "client infosec walked the data paths"
327
328
  fde ask acme --role eval_owner # or: fde waive acme client_readiness --reason "..."
328
329
 
329
330
  fde build acme --out project # refuses until the gates truly pass
330
- python project/evals/harness.py # red until implemented -- that's the exam
331
+ python project/evals/harness.py # red: empty until pairs are seeded, then red until implemented -- that's the exam
331
332
  fde implement project # drive a coding agent until it's green
332
333
  ```
333
334
 
@@ -389,8 +390,9 @@ the same receipts the emitted `ARCHITECTURE.md` prints.
389
390
 
390
391
  ## Troubleshooting
391
392
 
392
- **`no registry here`** — the default `--registry framework` is relative; run
393
- from the repository root or pass the path.
393
+ **`no registry here`** — you passed `--registry`/`--root` at a directory that
394
+ holds no registry. Drop the flag (the corpus ships inside the package) or
395
+ point it at the `framework/` of a source checkout.
394
396
 
395
397
  **`build` refuses with gates listed** — that is the point. `fde status`
396
398
  names each gate and its remedy; soft gates take `fde waive <gate> --reason`,
@@ -546,7 +548,7 @@ The registry is the shared asset; engagements are private working state.
546
548
  ```bash
547
549
  git clone https://github.com/atulkapoor/fde-framework.git && cd fde-framework
548
550
  python3 -m venv .venv && .venv/bin/pip install -e ".[dev,documents]"
549
- .venv/bin/pytest -q # ~850 tests, < 30s
551
+ .venv/bin/pytest -q # ~880 tests, < 60s
550
552
  .venv/bin/ruff check src tests
551
553
  ```
552
554
 
@@ -15,9 +15,9 @@
15
15
  <p>
16
16
  <a href="#install">Install</a> ·
17
17
  <a href="#try-it">Quickstart</a> ·
18
- <a href="ARCHITECTURE.md">Architecture</a> ·
19
- <a href="examples/invoice-extraction/">Worked example</a> ·
20
- <a href="CONTRIBUTING.md">Contributing</a> ·
18
+ <a href="https://github.com/atulkapoor/fde-framework/blob/main/ARCHITECTURE.md">Architecture</a> ·
19
+ <a href="https://github.com/atulkapoor/fde-framework/tree/main/examples/invoice-extraction">Worked example</a> ·
20
+ <a href="https://github.com/atulkapoor/fde-framework/blob/main/CONTRIBUTING.md">Contributing</a> ·
21
21
  <a href="https://pypi.org/project/fde-framework/">PyPI</a> ·
22
22
  <a href="https://atulkapoor.github.io/fde-framework/">Website</a>
23
23
  </p>
@@ -37,9 +37,9 @@ including on-premise, inside a customer VPC, or fully air-gapped.
37
37
  pip install fde-framework
38
38
  fde start acme --statement "Extract fields from supplier invoices."
39
39
  fde ask acme --role admin # role-scoped discovery interview
40
- fde architect acme # the design, with cited rationale
40
+ fde architect acme # topology + chosen approaches (rationale lands in ARCHITECTURE.md)
41
41
  fde build acme --out project # refuses: seven gates guard the build
42
- # ...verify data access, capture the baseline (each gate prints its remedy), then:
42
+ # ...verify data access, capture the baseline, name the eval owner (each gate prints its remedy), then:
43
43
  fde build acme --out project # code + evals + deploy assets + runbook
44
44
  ```
45
45
 
@@ -93,9 +93,9 @@ pairs, role-scoped interview, hardware scan) → fact log with provenance →
93
93
  permutation space → seven gates → decide → architect → build (code, evals,
94
94
  deploy and ops assets, `RISKS.md`, `COMPLIANCE.md`) → retro and case
95
95
  capture. Overrides are honoured on the next run, trigger observations feed
96
- calibration, and a reviewed case can enter the corpus. over 800 tests; three
97
- adversarial review rounds (108 findings, each fixed and pinned as a
98
- regression test); CI gates on the suite, lint, and a sanitisation scan of
96
+ calibration, and a reviewed case can enter the corpus. over 870 tests; four
97
+ fresh-eyes review rounds (142 findings, each resolved and the fixes pinned
98
+ as regression tests); CI gates on the suite, lint, and a sanitisation scan of
99
99
  the tree *and its history*; the evidence corpus is anchored to publicly
100
100
  documented production deployments; every decision is reproducible from its
101
101
  inputs.
@@ -227,8 +227,8 @@ fde scan engagements/acme # what this hardware runs
227
227
  fde cost --requests-per-day 500000 --model-b 70 # dated fleet sizing
228
228
 
229
229
  fde kb validate # parse and cross-link the registry
230
- fde kb gaps --root framework # what the corpus is missing
231
- fde kb sweep --root framework # profiles no approach can serve
230
+ fde kb gaps # what the corpus is missing
231
+ fde kb sweep # profiles no approach can serve
232
232
  .venv/bin/pytest -q
233
233
  ```
234
234
 
@@ -252,10 +252,11 @@ project/
252
252
  │ └── llm.py # the one model touchpoint — when a decision needs a model (boundary-gated)
253
253
  ├── evals/ # golden / edge / adversarial sets from the client's own pairs
254
254
  │ ├── harness.py # fails CI until implemented; judge-based when the evaluation decided judged
255
+ │ ├── retrieval.py # recall@10/50 of the retrieval layer alone — when retrieval answers ranked queries
255
256
  │ ├── acceptance.md # blind UAT protocol for the client's own judges
256
257
  │ └── load.py # p95 against the stated budget (when one was stated)
257
258
  ├── deploy/ # the substrate that was earned + TEARDOWN.md for all of it
258
- ├── ops/ # runbook keyed to the failure taxonomy, SLOs, rollback
259
+ ├── ops/ # runbook keyed to the failure taxonomy, diagnosis walk, SLOs, rollback
259
260
  ├── ARCHITECTURE.md # scope read-out, decisions, tools & alternatives, agent posture
260
261
  ├── RISKS.md # every waived gate and overridden recommendation
261
262
  └── COMPLIANCE.md # jurisdiction obligations, when a locale pack was applied
@@ -289,7 +290,7 @@ fde security-review acme --note "client infosec walked the data paths"
289
290
  fde ask acme --role eval_owner # or: fde waive acme client_readiness --reason "..."
290
291
 
291
292
  fde build acme --out project # refuses until the gates truly pass
292
- python project/evals/harness.py # red until implemented -- that's the exam
293
+ python project/evals/harness.py # red: empty until pairs are seeded, then red until implemented -- that's the exam
293
294
  fde implement project # drive a coding agent until it's green
294
295
  ```
295
296
 
@@ -351,8 +352,9 @@ the same receipts the emitted `ARCHITECTURE.md` prints.
351
352
 
352
353
  ## Troubleshooting
353
354
 
354
- **`no registry here`** — the default `--registry framework` is relative; run
355
- from the repository root or pass the path.
355
+ **`no registry here`** — you passed `--registry`/`--root` at a directory that
356
+ holds no registry. Drop the flag (the corpus ships inside the package) or
357
+ point it at the `framework/` of a source checkout.
356
358
 
357
359
  **`build` refuses with gates listed** — that is the point. `fde status`
358
360
  names each gate and its remedy; soft gates take `fde waive <gate> --reason`,
@@ -508,7 +510,7 @@ The registry is the shared asset; engagements are private working state.
508
510
  ```bash
509
511
  git clone https://github.com/atulkapoor/fde-framework.git && cd fde-framework
510
512
  python3 -m venv .venv && .venv/bin/pip install -e ".[dev,documents]"
511
- .venv/bin/pytest -q # ~850 tests, < 30s
513
+ .venv/bin/pytest -q # ~880 tests, < 60s
512
514
  .venv/bin/ruff check src tests
513
515
  ```
514
516
 
@@ -45,7 +45,7 @@ fde security-review engagements/acme --note "client infosec reviewed data paths
45
45
  fde waive engagements/acme client_readiness --reason "eval owner named, starts Monday"
46
46
 
47
47
  fde architect engagements/acme
48
- # topology on-prem [82d56c303de52199]
48
+ # topology on-prem [de84a4cea7cc42b9]
49
49
  # deployment systemd-unit via plain-python
50
50
  # evaluation field-match via plain-python
51
51
  # governance boundary-and-audit via plain-python
@@ -57,6 +57,7 @@ fde architect engagements/acme
57
57
 
58
58
  fde build engagements/acme --out project
59
59
  # wrote project
60
+ # next: fde implement project --holdout engagements/acme/artifacts/holdout.jsonl
60
61
  ```
61
62
 
62
63
  The emitted `project/` holds `app/` (pipeline in topological order, boundary
@@ -64,7 +65,7 @@ check imported at startup because data cannot leave), `evals/` (golden set
64
65
  from the three pairs, a harness that fails CI until the pipeline is
65
66
  implemented, and `acceptance.md` — a blind-judging protocol for the client's
66
67
  own people), `deploy/` (a systemd unit — rung zero, because nothing in the
67
- profile earned a container), `ops/` (runbook, SLOs, rollback),
68
+ profile earned a container), `ops/` (runbook, diagnosis walk, SLOs, rollback),
68
69
  `ARCHITECTURE.md` with the scope read-out, the tools table with
69
70
  in-topology alternatives, the agent posture, and every rejected
70
71
  alternative, and `RISKS.md` recording the one waived gate and its reason.
@@ -82,7 +83,7 @@ fde retro engagements/acme --outcome "delivered" --days 18
82
83
  Notice what the decisions did *not* do: no vector database for a lookup
83
84
  workload, no Kubernetes for a single service, no LLM in the extraction path
84
85
  while the deterministic mapper's coverage is unmeasured. The fingerprint
85
- `82d56c303de52199` is stable — rebuild from the same facts and the diff is
86
+ `de84a4cea7cc42b9` is stable — rebuild from the same facts and the diff is
86
87
  empty. (The corpus evolves, and a corpus change that moves a decision moves
87
88
  this fingerprint with it; the transcript above is re-run against the corpus
88
89
  it ships with.)
@@ -17,3 +17,9 @@ Not a fleet server: no continuous batching, no paged attention. The moment
17
17
  there are ten concurrent users, this is the wrong stack and vLLM (or SGLang,
18
18
  where requests share long prefixes) is the right one -- which is a swap of
19
19
  realization, not of architecture.
20
+
21
+ The ceiling is concurrency: generations serve from a sequential queue, so
22
+ overlapping requests stack their latencies. Right for a seat or a team;
23
+ past roughly a thousand requests a day the queue is the system -- move to
24
+ an engine with continuous batching before promising that rate, and
25
+ `fde scan <engagement>` says so from the recorded arrival rate.
@@ -0,0 +1,21 @@
1
+ ---
2
+ id: vllm
3
+ name: vLLM
4
+ licence: Apache-2.0
5
+ topologies: [customer-vpc, on-prem, air-gapped, hybrid]
6
+ last_verified: 2026-09-10
7
+ reversibility: moderate
8
+ ---
9
+ Serving open weights. Continuous batching and prefix caching by default.
10
+
11
+ What the defaults buy: the KV cache is paged rather than allocated as one
12
+ contiguous block per request, so memory fragments less and admits more
13
+ concurrent sequences; and the scheduler adds and retires requests mid-batch
14
+ instead of waiting for a batch to drain. Together they are why this engine
15
+ holds throughput when requests overlap -- and why a sequential single-box
16
+ runtime that benchmarked fine at one user falls over at twenty.
17
+
18
+ The flip side is operational: vLLM is a service somebody runs (CUDA stack,
19
+ versioned kernels), where the single-box path is one binary. The crossover
20
+ is measured, not guessed: the arrival rate the engagement recorded says
21
+ whether requests overlap, and `fde scan <engagement>` reads it.
@@ -45,7 +45,9 @@ LIMIT %(k)s;
45
45
  """
46
46
 
47
47
  # One hop per query, looped in the application: co-mentioned chunks of the
48
- # frontier, minus everything already found. Bounded by construction.
48
+ # frontier, minus everything already found. Depth is bounded by the loop and
49
+ # breadth by the pool cap -- depth alone is not a bound, because one hub
50
+ # entity can pull the whole corpus in on a single hop.
49
51
  EXPAND_SQL = """
50
52
  SELECT DISTINCT m2.chunk_id, c.body
51
53
  FROM {table}_mentions m1
@@ -72,6 +74,7 @@ class {{ class_name }}:
72
74
  dimensions: int = 768,
73
75
  max_hops: int = 2,
74
76
  seeds: int = 20,
77
+ max_pool: int = 200,
75
78
  ) -> None:
76
79
  self.connection = connection
77
80
  self.embed = embed
@@ -80,6 +83,7 @@ class {{ class_name }}:
80
83
  self.dimensions = dimensions
81
84
  self.max_hops = max_hops
82
85
  self.seeds = seeds
86
+ self.max_pool = max_pool
83
87
 
84
88
  def schema(self) -> str:
85
89
  return SCHEMA_SQL.format(table=self.table, dimensions=self.dimensions)
@@ -102,6 +106,7 @@ class {{ class_name }}:
102
106
  {"frontier": frontier, "found": list(found)},
103
107
  )
104
108
  fresh = cursor.fetchall()
109
+ fresh = fresh[: max(0, self.max_pool - len(found))]
105
110
  if not fresh:
106
111
  break
107
112
  for row in fresh:
@@ -20,8 +20,9 @@ approach = "{{ approach }}"
20
20
  stack = "{{ stack }}"
21
21
 
22
22
  TOP_K = 8
23
- SEEDS = 20 # vector recall opens the graph; it does not need to finish the job
24
- MAX_HOPS = 2 # each hop multiplies the pool, and two answers most questions
23
+ SEEDS = 20 # vector recall opens the graph; it does not need to finish the job
24
+ MAX_HOPS = 2 # each hop multiplies the pool, and two answers most questions
25
+ MAX_POOL = 200 # depth alone is not a bound: one hub entity can pull the corpus in
25
26
 
26
27
  # Injected at wiring time: retrieve(query, k) -> seed chunks (each a dict with
27
28
  # at least id, text, entities); rerank(query, text) -> relevance, higher is
@@ -54,6 +55,8 @@ class {{ class_name }}:
54
55
  ]
55
56
  fresh = {c["id"]: {**c, "hop": hop}
56
57
  for c in connected if c["id"] not in pool}
58
+ room = max(0, MAX_POOL - len(pool))
59
+ fresh = dict(list(fresh.items())[:room])
57
60
  if not fresh:
58
61
  break
59
62
  pool.update(fresh)
@@ -24,7 +24,8 @@ stack = "{{ stack }}"
24
24
  COLLECTION = "{{ component }}_chunks"
25
25
  TOP_K = 8
26
26
  SEEDS = 20
27
- MAX_HOPS = 2 # bounded: each hop multiplies latency, and two answers most questions
27
+ MAX_HOPS = 2 # bounded: each hop multiplies latency, and two answers most questions
28
+ MAX_POOL = 200 # depth alone is not a bound: one hub entity can pull the corpus in
28
29
 
29
30
 
30
31
  class QdrantUnwired(RuntimeError):
@@ -62,6 +63,7 @@ def run(query: str, top_k: int = TOP_K) -> list[dict]:
62
63
  for chunk_id in adjacency.get(entity, [])
63
64
  ]
64
65
  fresh = [c for c in dict.fromkeys(connected) if c not in found]
66
+ fresh = fresh[: max(0, MAX_POOL - len(found))]
65
67
  if not fresh:
66
68
  break
67
69
  records = client.retrieve(collection_name=COLLECTION, ids=fresh,
@@ -69,7 +71,11 @@ def run(query: str, top_k: int = TOP_K) -> list[dict]:
69
71
  for record in records:
70
72
  found[str(record.id)] = {"id": str(record.id), "hop": hop,
71
73
  **(record.payload or {})}
72
- frontier = fresh
74
+ # Only ids the store actually returned: on a corpus that keeps
75
+ # changing, the adjacency map can name a chunk deleted since the
76
+ # last rebuild, and walking from a ghost is a KeyError in the exact
77
+ # scenario this variant exists for.
78
+ frontier = [str(record.id) for record in records]
73
79
  scored = [
74
80
  {**c, "rerank_score": rerank(query, c.get("text", ""))}
75
81
  for c in found.values()
@@ -54,10 +54,10 @@ def run(query: str, top_k: int = TOP_K) -> list[dict]:
54
54
  frontier = list(found)
55
55
  for hop in range(1, MAX_HOPS + 1):
56
56
  connected = [
57
- chunk_id
57
+ neighbour
58
58
  for chunk_id in frontier
59
59
  for entity in found[chunk_id].get("entities", [])
60
- for chunk_id2 in adjacency.get(entity, [])
60
+ for neighbour in adjacency.get(entity, [])
61
61
  ]
62
62
  fresh = [c for c in dict.fromkeys(connected) if c not in found]
63
63
  if not fresh:
@@ -2,7 +2,7 @@
2
2
  # Distribution name matches the repository and stays unique on any index;
3
3
  # the import package and the CLI command remain the short `fde`.
4
4
  name = "fde-framework"
5
- version = "0.1.4"
5
+ version = "0.1.6"
6
6
  description = "A framework for Forward Deployed Engineers: from problem statement to a deployable AI project, every decision traced to a fact."
7
7
  readme = "README.md"
8
8
  license = {text = "Apache-2.0"}
@@ -53,7 +53,16 @@ class Architecture:
53
53
  return {s: v for s, v in self.licences.items() if copyleft(v)}
54
54
 
55
55
  def fingerprint(self) -> str:
56
- return self.decisions.decided_fingerprint()
56
+ """Topology and decisions together.
57
+
58
+ Two engagements with the same approach set but different topologies
59
+ build byte-different projects (the boundary code differs), and a
60
+ fingerprint that called them identical was answering a narrower
61
+ question than the one it prints beside."""
62
+ import hashlib
63
+
64
+ payload = f"{self.topology}:{self.decisions.decided_fingerprint()}"
65
+ return hashlib.sha256(payload.encode()).hexdigest()[:16]
57
66
 
58
67
 
59
68
  def architect(
@@ -592,6 +592,12 @@ def ask(
592
592
  continue
593
593
 
594
594
  dimension_entry = registry.dimensions[question.resolves]
595
+ # A multi-valued dimension never enters the answer space (a second
596
+ # value is a peer, not a contradiction), so the space alone would
597
+ # offer this question again immediately -- asked and answered is
598
+ # asked and answered for this session; a later session can add more.
599
+ if dimension_entry.multi_valued or isinstance(answer.value, tuple):
600
+ passed_on.add(question.resolves)
595
601
  answered_values = (
596
602
  list(answer.value) if isinstance(answer.value, tuple) else [answer.value]
597
603
  )
@@ -1517,9 +1523,15 @@ def build_cmd(
1517
1523
  ]
1518
1524
  report = emit(architecture, out, registry=registry,
1519
1525
  templates=Path(registry_root) / "templates",
1520
- pairs_path=Path(root) / "artifacts" / "pairs.jsonl",
1526
+ # The engagement's own root, never the raw argument: a
1527
+ # bare name (`fde build receipts`) resolves through
1528
+ # _engagement, and building the pairs path from the raw
1529
+ # argument silently emitted a project with an empty
1530
+ # golden set while 42 verified pairs sat on disk.
1531
+ pairs_path=engagement.root / "artifacts" / "pairs.jsonl",
1521
1532
  waivers=waivers,
1522
- overrides=applied_overrides)
1533
+ overrides=applied_overrides,
1534
+ baseline=engagement.baseline())
1523
1535
  except BuildRefused as exc:
1524
1536
  typer.echo(f"refused: {exc}", err=True)
1525
1537
  raise typer.Exit(1) from exc
@@ -1554,6 +1566,22 @@ def build_cmd(
1554
1566
  _write_compliance(Path(out), locale)
1555
1567
 
1556
1568
  typer.echo(f"wrote {out}")
1569
+ # The exam's size belongs in the build receipt: an empty golden set
1570
+ # emitted next to sixty pairs on disk once read as a finished build.
1571
+ counts = {}
1572
+ for layer in ("golden", "edge_case", "adversarial"):
1573
+ layer_path = Path(out) / "evals" / f"{layer}.jsonl"
1574
+ counts[layer] = sum(
1575
+ 1 for line in layer_path.read_text().splitlines() if line.strip()
1576
+ ) if layer_path.exists() else 0
1577
+ typer.echo(f"evals: {counts['golden']} golden, {counts['edge_case']} edge, "
1578
+ f"{counts['adversarial']} adversarial")
1579
+ if not counts["golden"] and (engagement.root / "artifacts" / "pairs.jsonl").exists():
1580
+ typer.echo(
1581
+ " pairs exist on the engagement but none reached the golden set -- "
1582
+ "mark checked pairs `verified: true` and re-run `fde samples`",
1583
+ err=True,
1584
+ )
1557
1585
  # The delivery is finishable, and the finishing move is one command.
1558
1586
  holdout_path = engagement.root / "artifacts" / "holdout.jsonl"
1559
1587
  hint = f"fde implement {out}"
@@ -1648,7 +1676,15 @@ def scan_cmd(
1648
1676
  # costing figure, because model releases move monthly.
1649
1677
  from fde.scan import MODEL_GUIDANCE_AS_OF, recommend_local_models
1650
1678
 
1651
- plan = recommend_local_models(hardware)
1679
+ # The engine choice is a concurrency decision as much as a hardware
1680
+ # one: when the engagement recorded an arrival rate, it flows in.
1681
+ arrival = None
1682
+ if root is not None:
1683
+ try:
1684
+ arrival = _engagement(root).profile.get("arrival_rate")
1685
+ except Exception: # noqa: BLE001 -- a scan without an engagement still scans
1686
+ arrival = None
1687
+ plan = recommend_local_models(hardware, arrival_per_day=arrival)
1652
1688
  typer.echo(f"\nlocal models (guidance as of {MODEL_GUIDANCE_AS_OF} -- "
1653
1689
  f"releases move monthly, verify before install)")
1654
1690
  typer.echo(f" runtime: {plan.runtime} -- {plan.runtime_reason}")
@@ -111,9 +111,22 @@ def decide_component(
111
111
  )
112
112
  continue
113
113
  if not any(holds(c, profile, registry) for c in approach.applies_when):
114
- rejected.append(
115
- Rejected(approach.id, f"nothing here matches {' or '.join(approach.applies_when)}")
116
- )
114
+ reason = f"nothing here matches {' or '.join(approach.applies_when)}"
115
+ # An unanswered dimension is a different situation from a
116
+ # mismatched one: the reader should know one question could
117
+ # change this rejection, not go looking for a design flaw.
118
+ open_dimensions = sorted({
119
+ dimension
120
+ for condition in approach.applies_when
121
+ for dimension in _referenced(condition)
122
+ if profile.get(dimension) is None
123
+ })
124
+ if open_dimensions:
125
+ reason += (
126
+ f" -- unanswered: {', '.join(open_dimensions)} "
127
+ f"(an answer could admit it)"
128
+ )
129
+ rejected.append(Rejected(approach.id, reason))
117
130
  # Not ruled out -- just not ruled in. If its applies conditions
118
131
  # reference something unanswered, an answer could still admit it.
119
132
  still_askable.append(approach)