fde-framework 0.1.1__tar.gz → 0.1.2__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 (338) hide show
  1. {fde_framework-0.1.1 → fde_framework-0.1.2}/CHANGELOG.md +20 -0
  2. {fde_framework-0.1.1 → fde_framework-0.1.2}/PKG-INFO +86 -17
  3. {fde_framework-0.1.1 → fde_framework-0.1.2}/README.md +85 -16
  4. {fde_framework-0.1.1 → fde_framework-0.1.2}/pyproject.toml +1 -1
  5. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/cli.py +113 -54
  6. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/emit.py +34 -0
  7. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/gates.py +17 -10
  8. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/implement.py +15 -4
  9. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/registry.py +22 -0
  10. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_emit.py +9 -0
  11. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_sanitisation.py +11 -3
  12. {fde_framework-0.1.1 → fde_framework-0.1.2}/.gitignore +0 -0
  13. {fde_framework-0.1.1 → fde_framework-0.1.2}/LICENSE +0 -0
  14. {fde_framework-0.1.1 → fde_framework-0.1.2}/examples/invoice-extraction/README.md +0 -0
  15. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/ansible-playbook.md +0 -0
  16. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/assisted-deterministic.md +0 -0
  17. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/audit-only.md +0 -0
  18. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/boundary-and-audit.md +0 -0
  19. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/cascade.md +0 -0
  20. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/classical-ml.md +0 -0
  21. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/compose.md +0 -0
  22. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/decision-log.md +0 -0
  23. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/deterministic-masking.md +0 -0
  24. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/deterministic.md +0 -0
  25. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/direct-call.md +0 -0
  26. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/episodic-store.md +0 -0
  27. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/explainability-record.md +0 -0
  28. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/field-match.md +0 -0
  29. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/finetune.md +0 -0
  30. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/fixed-sequence.md +0 -0
  31. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/gitops.md +0 -0
  32. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/governed-tools.md +0 -0
  33. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/graph-retrieval.md +0 -0
  34. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/judged.md +0 -0
  35. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/keyword-search.md +0 -0
  36. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/kubernetes-manifests.md +0 -0
  37. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/labelled-metrics.md +0 -0
  38. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/llm-extraction.md +0 -0
  39. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/llm-scrubbing.md +0 -0
  40. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/llm.md +0 -0
  41. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/local-embedding.md +0 -0
  42. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/managed-api.md +0 -0
  43. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/managed-embedding.md +0 -0
  44. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/manual-runbook.md +0 -0
  45. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/model-planner.md +0 -0
  46. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/ocr-pipeline.md +0 -0
  47. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/optimisation-reasoning.md +0 -0
  48. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/optimisation.md +0 -0
  49. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/passthrough.md +0 -0
  50. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/role-scoped-authority.md +0 -0
  51. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/segmentation.md +0 -0
  52. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/self-hosted.md +0 -0
  53. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/serverless-gpu.md +0 -0
  54. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/speech-transcription.md +0 -0
  55. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/structured-logs.md +0 -0
  56. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/systemd-unit.md +0 -0
  57. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/terraform-module.md +0 -0
  58. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/text-extraction.md +0 -0
  59. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/traced.md +0 -0
  60. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/vector-search.md +0 -0
  61. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/video-ingestion.md +0 -0
  62. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/windowed-ingestion.md +0 -0
  63. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/approaches/working-state.md +0 -0
  64. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/cases/churn-scoring.md +0 -0
  65. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/cases/route-planning.md +0 -0
  66. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/cases/structured-extraction.md +0 -0
  67. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/cases/studio-style.md +0 -0
  68. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/components/accountability.md +0 -0
  69. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/components/deployment.md +0 -0
  70. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/components/embedding.md +0 -0
  71. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/components/evaluation.md +0 -0
  72. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/components/governance.md +0 -0
  73. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/components/integration.md +0 -0
  74. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/components/memory.md +0 -0
  75. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/components/observability.md +0 -0
  76. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/components/perception.md +0 -0
  77. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/components/planning.md +0 -0
  78. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/components/provisioning.md +0 -0
  79. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/components/reasoning.md +0 -0
  80. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/components/redaction.md +0 -0
  81. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/components/representation.md +0 -0
  82. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/components/retrieval.md +0 -0
  83. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/components/serving.md +0 -0
  84. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/accelerator.md +0 -0
  85. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/access_model.md +0 -0
  86. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/arrival_rate.md +0 -0
  87. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/availability_target.md +0 -0
  88. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/cheap_path_coverage.md +0 -0
  89. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/confidence_calibrated.md +0 -0
  90. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/container_competence.md +0 -0
  91. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/corpus_size.md +0 -0
  92. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/data_residency.md +0 -0
  93. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/environment_lifetime.md +0 -0
  94. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/existing_cluster.md +0 -0
  95. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/existing_iac_tool.md +0 -0
  96. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/external_systems.md +0 -0
  97. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/hosting.md +0 -0
  98. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/human_waiting.md +0 -0
  99. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/input_format.md +0 -0
  100. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/interpretability_required.md +0 -0
  101. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/labelled_count.md +0 -0
  102. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/latency_budget_ms.md +0 -0
  103. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/licence_posture.md +0 -0
  104. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/operates_after_handover.md +0 -0
  105. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/output_shape.md +0 -0
  106. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/provisioning_api.md +0 -0
  107. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/query_pattern.md +0 -0
  108. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/recall_span.md +0 -0
  109. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/dimensions/sensitivity_present.md +0 -0
  110. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/interfaces/Generator.md +0 -0
  111. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/interfaces/Guard.md +0 -0
  112. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/interfaces/Mapper.md +0 -0
  113. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/interfaces/ModelServer.md +0 -0
  114. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/interfaces/Parser.md +0 -0
  115. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/interfaces/Planner.md +0 -0
  116. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/interfaces/Retriever.md +0 -0
  117. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/interfaces/Scorer.md +0 -0
  118. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/interfaces/Store.md +0 -0
  119. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/interfaces/ToolBoundary.md +0 -0
  120. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/interfaces/Tracer.md +0 -0
  121. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/locales/eu-gdpr.md +0 -0
  122. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/locales/in-dpdp.md +0 -0
  123. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/ansible-playbook.md +0 -0
  124. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/assisted-deterministic.md +0 -0
  125. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/audit-only.md +0 -0
  126. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/boundary-and-audit.md +0 -0
  127. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/cascade-reasoning.md +0 -0
  128. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/cascade-representation.md +0 -0
  129. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/cascade-retrieval.md +0 -0
  130. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/classical-ml-reasoning.md +0 -0
  131. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/classical-ml.md +0 -0
  132. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/compose.md +0 -0
  133. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/decision-log.md +0 -0
  134. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/deterministic-masking.md +0 -0
  135. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/deterministic.md +0 -0
  136. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/direct-call.md +0 -0
  137. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/episodic-store.md +0 -0
  138. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/explainability-record.md +0 -0
  139. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/field-match.md +0 -0
  140. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/finetune-representation.md +0 -0
  141. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/finetune.md +0 -0
  142. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/fixed-sequence.md +0 -0
  143. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/gitops.md +0 -0
  144. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/governed-tools.md +0 -0
  145. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/graph-retrieval.md +0 -0
  146. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/judged.md +0 -0
  147. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/keyword-search.md +0 -0
  148. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/kubernetes-manifests.md +0 -0
  149. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/labelled-metrics.md +0 -0
  150. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/llm-representation.md +0 -0
  151. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/llm-scrubbing.md +0 -0
  152. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/llm.md +0 -0
  153. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/local-embedding.md +0 -0
  154. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/managed-api.md +0 -0
  155. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/managed-embedding.md +0 -0
  156. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/manual-runbook.md +0 -0
  157. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/model-planner.md +0 -0
  158. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/ocr-pipeline.md +0 -0
  159. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/optimisation-reasoning.md +0 -0
  160. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/optimisation.md +0 -0
  161. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/passthrough.md +0 -0
  162. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/role-scoped-authority.md +0 -0
  163. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/segmentation.md +0 -0
  164. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/self-hosted.md +0 -0
  165. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/serverless-gpu.md +0 -0
  166. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/speech-transcription.md +0 -0
  167. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/structured-logs.md +0 -0
  168. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/systemd-unit.md +0 -0
  169. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/terraform-module.md +0 -0
  170. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/text-extraction.md +0 -0
  171. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/traced.md +0 -0
  172. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/vector-search.md +0 -0
  173. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/video-ingestion.md +0 -0
  174. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/windowed-ingestion.md +0 -0
  175. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/patterns/working-state.md +0 -0
  176. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/stacks/langgraph.md +0 -0
  177. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/stacks/local-judge.md +0 -0
  178. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/stacks/mcp.md +0 -0
  179. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/stacks/ollama.md +0 -0
  180. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/stacks/openai-judge.md +0 -0
  181. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/stacks/opentelemetry.md +0 -0
  182. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/stacks/ortools.md +0 -0
  183. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/stacks/pgvector.md +0 -0
  184. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/stacks/plain-python.md +0 -0
  185. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/stacks/qdrant.md +0 -0
  186. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/stacks/tesseract.md +0 -0
  187. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/stacks/vllm.md +0 -0
  188. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/stacks/whisper.md +0 -0
  189. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/stacks/xgboost.md +0 -0
  190. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/accountability/decision-log.plain.py.j2 +0 -0
  191. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/accountability/explainability-record.plain.py.j2 +0 -0
  192. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/deployment/compose.plain.py.j2 +0 -0
  193. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/deployment/kubernetes-manifests.plain.py.j2 +0 -0
  194. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/deployment/systemd-unit.plain.py.j2 +0 -0
  195. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/embedding/local-embedding.plain.py.j2 +0 -0
  196. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/embedding/managed-embedding.plain.py.j2 +0 -0
  197. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/evaluation/field-match.plain.py.j2 +0 -0
  198. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/evaluation/judged.local-judge.py.j2 +0 -0
  199. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/evaluation/judged.openai-judge.py.j2 +0 -0
  200. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/evaluation/judged.plain.py.j2 +0 -0
  201. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/evaluation/labelled-metrics.plain.py.j2 +0 -0
  202. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/evaluation/labelled-metrics.xgboost.py.j2 +0 -0
  203. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/governance/audit-only.plain.py.j2 +0 -0
  204. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/governance/boundary-and-audit.plain.py.j2 +0 -0
  205. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/governance/role-scoped-authority.plain.py.j2 +0 -0
  206. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/integration/direct-call.plain.py.j2 +0 -0
  207. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/integration/governed-tools.mcp.py.j2 +0 -0
  208. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/integration/governed-tools.plain.py.j2 +0 -0
  209. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/memory/episodic-store.pgvector.py.j2 +0 -0
  210. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/memory/episodic-store.plain.py.j2 +0 -0
  211. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/memory/working-state.plain.py.j2 +0 -0
  212. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/observability/structured-logs.plain.py.j2 +0 -0
  213. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/observability/traced.opentelemetry.py.j2 +0 -0
  214. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/observability/traced.plain.py.j2 +0 -0
  215. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/perception/ocr-pipeline.plain.py.j2 +0 -0
  216. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/perception/ocr-pipeline.tesseract.py.j2 +0 -0
  217. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/perception/passthrough.plain.py.j2 +0 -0
  218. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/perception/speech-transcription.plain.py.j2 +0 -0
  219. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/perception/speech-transcription.whisper.py.j2 +0 -0
  220. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/perception/text-extraction.plain.py.j2 +0 -0
  221. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/perception/video-ingestion.plain.py.j2 +0 -0
  222. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/perception/windowed-ingestion.plain.py.j2 +0 -0
  223. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/planning/fixed-sequence.plain.py.j2 +0 -0
  224. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/planning/model-planner.langgraph.py.j2 +0 -0
  225. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/planning/model-planner.plain.py.j2 +0 -0
  226. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/planning/optimisation.ortools.py.j2 +0 -0
  227. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/planning/optimisation.plain.py.j2 +0 -0
  228. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/provisioning/ansible-playbook.plain.py.j2 +0 -0
  229. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/provisioning/gitops.plain.py.j2 +0 -0
  230. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/provisioning/manual-runbook.plain.py.j2 +0 -0
  231. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/provisioning/terraform-module.plain.py.j2 +0 -0
  232. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/reasoning/cascade.plain.py.j2 +0 -0
  233. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/reasoning/classical-ml.plain.py.j2 +0 -0
  234. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/reasoning/classical-ml.xgboost.py.j2 +0 -0
  235. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/reasoning/finetune.plain.py.j2 +0 -0
  236. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/reasoning/llm.plain.py.j2 +0 -0
  237. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/reasoning/optimisation.ortools.py.j2 +0 -0
  238. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/reasoning/optimisation.plain.py.j2 +0 -0
  239. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/redaction/deterministic-masking.plain.py.j2 +0 -0
  240. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/redaction/llm-scrubbing.plain.py.j2 +0 -0
  241. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/representation/assisted.plain.py.j2 +0 -0
  242. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/representation/cascade.plain.py.j2 +0 -0
  243. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/representation/classical-ml.plain.py.j2 +0 -0
  244. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/representation/classical-ml.xgboost.py.j2 +0 -0
  245. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/representation/deterministic.plain.py.j2 +0 -0
  246. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/representation/finetune.plain.py.j2 +0 -0
  247. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/representation/llm.plain.py.j2 +0 -0
  248. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/representation/segmentation.plain.py.j2 +0 -0
  249. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/retrieval/cascade.plain.py.j2 +0 -0
  250. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/retrieval/graph-retrieval.pgvector.py.j2 +0 -0
  251. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/retrieval/graph-retrieval.plain.py.j2 +0 -0
  252. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/retrieval/graph-retrieval.qdrant.py.j2 +0 -0
  253. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/retrieval/keyword-search.plain.py.j2 +0 -0
  254. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/retrieval/vector-search.pgvector.py.j2 +0 -0
  255. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/retrieval/vector-search.plain.py.j2 +0 -0
  256. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/retrieval/vector-search.qdrant.py.j2 +0 -0
  257. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/serving/managed-api.plain.py.j2 +0 -0
  258. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/serving/self-hosted.ollama.py.j2 +0 -0
  259. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/serving/self-hosted.plain.py.j2 +0 -0
  260. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/serving/self-hosted.vllm.py.j2 +0 -0
  261. {fde_framework-0.1.1 → fde_framework-0.1.2}/framework/templates/serving/serverless-gpu.plain.py.j2 +0 -0
  262. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/__init__.py +0 -0
  263. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/architect.py +0 -0
  264. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/costing.py +0 -0
  265. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/decide.py +0 -0
  266. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/decompose.py +0 -0
  267. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/deploy.py +0 -0
  268. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/evolution.py +0 -0
  269. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/factlog.py +0 -0
  270. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/graph.py +0 -0
  271. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/intake/__init__.py +0 -0
  272. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/intake/answers.py +0 -0
  273. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/intake/documents.py +0 -0
  274. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/intake/interview.py +0 -0
  275. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/intake/llm_reader.py +0 -0
  276. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/intake/prose.py +0 -0
  277. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/intake/samples.py +0 -0
  278. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/models/__init__.py +0 -0
  279. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/models/base.py +0 -0
  280. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/models/fact.py +0 -0
  281. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/models/profile.py +0 -0
  282. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/models/respondent.py +0 -0
  283. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/models/schema.py +0 -0
  284. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/moves.py +0 -0
  285. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/ops.py +0 -0
  286. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/predicate.py +0 -0
  287. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/realization.py +0 -0
  288. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/scan.py +0 -0
  289. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/space.py +0 -0
  290. {fde_framework-0.1.1 → fde_framework-0.1.2}/src/fde/workflow.py +0 -0
  291. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/__init__.py +0 -0
  292. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/conftest.py +0 -0
  293. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/models/__init__.py +0 -0
  294. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/models/test_profile.py +0 -0
  295. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_access_and_sensitivity.py +0 -0
  296. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_answers.py +0 -0
  297. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_cascade.py +0 -0
  298. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_cli_engagement.py +0 -0
  299. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_cli_evolution.py +0 -0
  300. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_cli_gates.py +0 -0
  301. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_cli_intake.py +0 -0
  302. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_cli_kb.py +0 -0
  303. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_cli_robustness.py +0 -0
  304. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_cli_samples.py +0 -0
  305. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_costing.py +0 -0
  306. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_coverage.py +0 -0
  307. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_dead_zones.py +0 -0
  308. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_decide.py +0 -0
  309. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_decompose.py +0 -0
  310. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_deploy.py +0 -0
  311. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_documents.py +0 -0
  312. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_embedding.py +0 -0
  313. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_evolution.py +0 -0
  314. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_factlog.py +0 -0
  315. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_gates.py +0 -0
  316. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_implement.py +0 -0
  317. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_interview.py +0 -0
  318. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_llm_reader.py +0 -0
  319. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_locales.py +0 -0
  320. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_memory_retrieval.py +0 -0
  321. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_more_templates.py +0 -0
  322. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_moves.py +0 -0
  323. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_multimodal.py +0 -0
  324. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_ops.py +0 -0
  325. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_package.py +0 -0
  326. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_prose.py +0 -0
  327. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_realization.py +0 -0
  328. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_registry.py +0 -0
  329. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_remaining.py +0 -0
  330. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_reversibility.py +0 -0
  331. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_samples.py +0 -0
  332. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_scan.py +0 -0
  333. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_schema.py +0 -0
  334. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_serving.py +0 -0
  335. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_shipped_registry.py +0 -0
  336. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_space.py +0 -0
  337. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_stack_swap.py +0 -0
  338. {fde_framework-0.1.1 → fde_framework-0.1.2}/tests/test_templates.py +0 -0
@@ -5,6 +5,26 @@ 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.2] — 2026-09-07
9
+
10
+ A fresh-eyes usability round, every finding verified in a clean venv:
11
+
12
+ - The hero quickstart runs verbatim: bare engagement names resolve
13
+ (`fde ask acme` finds `engagements/acme`), and `fde start` reads its
14
+ own statement through the prose reader, so the first minute shows
15
+ typed facts instead of "no facts recorded yet".
16
+ - `fde --version`; interview questions show their legal values with the
17
+ question; gate remedies name their clearing commands; the baseline
18
+ refusal names the exact keys that satisfy it.
19
+ - `fde implement` without an agent on PATH is a sentence with the fix,
20
+ never a traceback; `fde cost --price-per-seat` works without fleet
21
+ flags.
22
+ - kb subcommands take `--registry` (with `--root` kept as an alias).
23
+ - Every emitted project gets a front-door README; images are PNG so
24
+ they render on PyPI and mobile; the README gains a copy-paste full
25
+ lifecycle with the baseline YAML inline, and a Python API section
26
+ backed by the public `fde.registry.default_root()`.
27
+
8
28
  ## [0.1.1] — 2026-09-06
9
29
 
10
30
  - README images render everywhere: absolute URLs, because PyPI does not
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: fde-framework
3
- Version: 0.1.1
3
+ Version: 0.1.2
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
@@ -36,8 +36,8 @@ Requires-Dist: anthropic>=0.40; extra == 'llm'
36
36
  Description-Content-Type: text/markdown
37
37
 
38
38
  <picture>
39
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/atulkapoor/fde-framework/main/assets/banner-dark.svg">
40
- <img src="https://raw.githubusercontent.com/atulkapoor/fde-framework/main/assets/banner-light.svg" alt="fde — a framework for Forward Deployed Engineers" width="760">
39
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/atulkapoor/fde-framework/main/assets/banner-dark.png">
40
+ <img src="https://raw.githubusercontent.com/atulkapoor/fde-framework/main/assets/banner-light.png" alt="fde — a framework for Forward Deployed Engineers" width="760">
41
41
  </picture>
42
42
 
43
43
  # fde — a framework for Forward Deployed Engineers
@@ -49,6 +49,15 @@ Description-Content-Type: text/markdown
49
49
  ![Status](https://img.shields.io/badge/status-alpha-orange)
50
50
  [![License](https://img.shields.io/badge/license-Apache--2.0-green)](LICENSE)
51
51
 
52
+ <p>
53
+ <a href="#install">Install</a> ·
54
+ <a href="#try-it-in-two-minutes">Quickstart</a> ·
55
+ <a href="ARCHITECTURE.md">Architecture</a> ·
56
+ <a href="examples/invoice-extraction/">Worked example</a> ·
57
+ <a href="CONTRIBUTING.md">Contributing</a> ·
58
+ <a href="https://pypi.org/project/fde-framework/">PyPI</a>
59
+ </p>
60
+
52
61
  **fde** is an open-source framework for Forward Deployed Engineers: it takes a
53
62
  client engagement from a problem statement to a runnable, deployable AI
54
63
  project — with every decision traced to a fact, and every fact traced to a
@@ -68,7 +77,7 @@ fde architect acme # the design, with cited rationale
68
77
  fde build acme --out project # code + evals + deploy assets + runbook
69
78
  ```
70
79
 
71
- <img src="https://raw.githubusercontent.com/atulkapoor/fde-framework/main/assets/demo.svg" alt="fde in a terminal: a paragraph becomes typed facts, an architecture with a fingerprint, and a build that refuses until the hard gate passes" width="780">
80
+ <img src="https://raw.githubusercontent.com/atulkapoor/fde-framework/main/assets/demo.png" alt="fde in a terminal: a paragraph becomes typed facts, an architecture with a fingerprint, and a build that refuses until the hard gate passes" width="780">
72
81
 
73
82
  *That refusal at the end is the product working: no baseline, no verified data
74
83
  access — no build. The remedies ship with every gate.*
@@ -232,18 +241,18 @@ refuses by name and says exactly what to install.
232
241
  ## Try it
233
242
 
234
243
  ```bash
235
- .venv/bin/fde start acme --statement "Extract fields from supplier invoices."
236
- .venv/bin/fde ask engagements/acme --role admin # role-scoped interview
237
- .venv/bin/fde status engagements/acme # gates, gaps, disagreements
238
- .venv/bin/fde architect engagements/acme # the design, with rationale
239
- .venv/bin/fde build engagements/acme --out project # refuses until gates clear
240
-
241
- .venv/bin/fde scan engagements/acme # what this hardware runs
242
- .venv/bin/fde cost --requests-per-day 500000 --model-b 70 # dated fleet sizing
243
-
244
- .venv/bin/fde kb validate --root framework # parse and cross-link the registry
245
- .venv/bin/fde kb gaps --root framework # what the corpus is missing
246
- .venv/bin/fde kb sweep --root framework # profiles no approach can serve
244
+ fde start acme --statement "Extract fields from supplier invoices."
245
+ fde ask engagements/acme --role admin # role-scoped interview
246
+ fde status engagements/acme # gates, gaps, disagreements
247
+ fde architect engagements/acme # the design, with rationale
248
+ fde build engagements/acme --out project # refuses until gates clear
249
+
250
+ fde scan engagements/acme # what this hardware runs
251
+ fde cost --requests-per-day 500000 --model-b 70 # dated fleet sizing
252
+
253
+ fde kb validate # parse and cross-link the registry
254
+ fde kb gaps --root framework # what the corpus is missing
255
+ fde kb sweep --root framework # profiles no approach can serve
247
256
  .venv/bin/pytest -q
248
257
  ```
249
258
 
@@ -276,6 +285,66 @@ project/
276
285
  └── COMPLIANCE.md # jurisdiction obligations, when a locale pack was applied
277
286
  ```
278
287
 
288
+ ## The full lifecycle, copy-paste
289
+
290
+ Everything below runs from an empty directory after `pip install fde-framework`:
291
+
292
+ ```bash
293
+ fde start acme --statement "Extract fields from scanned supplier invoices; \
294
+ data cannot leave; 200,000 documents, 8,000 verified; a person is waiting."
295
+ # plays back the typed facts it read, and the three questions worth asking next
296
+
297
+ fde ask acme --role eval_owner # answer what discovery still needs
298
+ fde status acme # facts by scope, gates, % settled
299
+
300
+ cat > baseline.yaml <<'YAML'
301
+ volume: {value: 20000, unit: docs/month, definition: invoices received by AP}
302
+ cycle_time_per_unit_seconds: {value: 300, unit: s, definition: arrival to posted}
303
+ labour_hours_per_week: {value: 35, unit: h/week, definition: AP team keying time}
304
+ rework_rate: {value: 0.1, unit: ratio, definition: entries corrected after post}
305
+ exception_rate: {value: 0.07, unit: ratio, definition: routed to a human queue}
306
+ error_rate: {value: 0.04, unit: ratio, definition: wrong amount or vendor posted}
307
+ business_metric: {value: 9, unit: days, definition: mean days payable outstanding}
308
+ sampled: {n: 40, method: random invoices across two months}
309
+ YAML
310
+ fde baseline acme --file baseline.yaml
311
+ fde data-access acme --note "read replica returned 14 real rows"
312
+ fde security-review acme --note "client infosec walked the data paths"
313
+ fde ask acme --role eval_owner # or: fde waive acme client_readiness --reason "..."
314
+
315
+ fde build acme --out project # refuses until the gates truly pass
316
+ python project/evals/harness.py # red until implemented -- that's the exam
317
+ fde implement project # drive a coding agent until it's green
318
+ ```
319
+
320
+ ## Python API
321
+
322
+ The CLI is a thin layer; everything is importable. The registry loads from the
323
+ installed wheel, so this runs anywhere:
324
+
325
+ ```python
326
+ from fde.architect import architect
327
+ from fde.intake.prose import parse_prose
328
+ from fde.models.profile import Profile
329
+ from fde.registry import default_root, load_registry
330
+
331
+ registry = load_registry(default_root())
332
+
333
+ profile = Profile()
334
+ profile.ingest(parse_prose(
335
+ "500,000 scanned invoices; data cannot leave; 10,000 verified; "
336
+ "a person is waiting; structured records out.", registry))
337
+
338
+ design = architect(profile, registry)
339
+ print(design.topology) # customer-vpc
340
+ for component, decision in sorted(design.decisions.items()):
341
+ if decision.approach:
342
+ print(component, decision.approach, decision.rationale)
343
+ ```
344
+
345
+ Every decision object carries its rationale and its rejected alternatives —
346
+ the same receipts the emitted `ARCHITECTURE.md` prints.
347
+
279
348
  ## Common commands
280
349
 
281
350
  | | |
@@ -429,7 +498,7 @@ The registry is the shared asset; engagements are private working state.
429
498
  deliberately waits for a corpus of measured retrospectives rather than
430
499
  pretending a handful is evidence.
431
500
  - **More locale packs and stacks** — both are data; contributions enter
432
- against [CONTRIBUTING.md](CONTRIBUTING.md)'s contract.
501
+ against [CONTRIBUTING.md](CONTRIBUTING.md)'s contract (and the [code of conduct](CODE_OF_CONDUCT.md)).
433
502
  - **Language, channel, and device axes** — six of twenty industry test
434
503
  statements named regional languages, low bandwidth, or basic devices;
435
504
  the honest wiring (per-language evaluation, SMS/IVR serving approaches,
@@ -1,6 +1,6 @@
1
1
  <picture>
2
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/atulkapoor/fde-framework/main/assets/banner-dark.svg">
3
- <img src="https://raw.githubusercontent.com/atulkapoor/fde-framework/main/assets/banner-light.svg" alt="fde — a framework for Forward Deployed Engineers" width="760">
2
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/atulkapoor/fde-framework/main/assets/banner-dark.png">
3
+ <img src="https://raw.githubusercontent.com/atulkapoor/fde-framework/main/assets/banner-light.png" alt="fde — a framework for Forward Deployed Engineers" width="760">
4
4
  </picture>
5
5
 
6
6
  # fde — a framework for Forward Deployed Engineers
@@ -12,6 +12,15 @@
12
12
  ![Status](https://img.shields.io/badge/status-alpha-orange)
13
13
  [![License](https://img.shields.io/badge/license-Apache--2.0-green)](LICENSE)
14
14
 
15
+ <p>
16
+ <a href="#install">Install</a> ·
17
+ <a href="#try-it-in-two-minutes">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> ·
21
+ <a href="https://pypi.org/project/fde-framework/">PyPI</a>
22
+ </p>
23
+
15
24
  **fde** is an open-source framework for Forward Deployed Engineers: it takes a
16
25
  client engagement from a problem statement to a runnable, deployable AI
17
26
  project — with every decision traced to a fact, and every fact traced to a
@@ -31,7 +40,7 @@ fde architect acme # the design, with cited rationale
31
40
  fde build acme --out project # code + evals + deploy assets + runbook
32
41
  ```
33
42
 
34
- <img src="https://raw.githubusercontent.com/atulkapoor/fde-framework/main/assets/demo.svg" alt="fde in a terminal: a paragraph becomes typed facts, an architecture with a fingerprint, and a build that refuses until the hard gate passes" width="780">
43
+ <img src="https://raw.githubusercontent.com/atulkapoor/fde-framework/main/assets/demo.png" alt="fde in a terminal: a paragraph becomes typed facts, an architecture with a fingerprint, and a build that refuses until the hard gate passes" width="780">
35
44
 
36
45
  *That refusal at the end is the product working: no baseline, no verified data
37
46
  access — no build. The remedies ship with every gate.*
@@ -195,18 +204,18 @@ refuses by name and says exactly what to install.
195
204
  ## Try it
196
205
 
197
206
  ```bash
198
- .venv/bin/fde start acme --statement "Extract fields from supplier invoices."
199
- .venv/bin/fde ask engagements/acme --role admin # role-scoped interview
200
- .venv/bin/fde status engagements/acme # gates, gaps, disagreements
201
- .venv/bin/fde architect engagements/acme # the design, with rationale
202
- .venv/bin/fde build engagements/acme --out project # refuses until gates clear
203
-
204
- .venv/bin/fde scan engagements/acme # what this hardware runs
205
- .venv/bin/fde cost --requests-per-day 500000 --model-b 70 # dated fleet sizing
206
-
207
- .venv/bin/fde kb validate --root framework # parse and cross-link the registry
208
- .venv/bin/fde kb gaps --root framework # what the corpus is missing
209
- .venv/bin/fde kb sweep --root framework # profiles no approach can serve
207
+ fde start acme --statement "Extract fields from supplier invoices."
208
+ fde ask engagements/acme --role admin # role-scoped interview
209
+ fde status engagements/acme # gates, gaps, disagreements
210
+ fde architect engagements/acme # the design, with rationale
211
+ fde build engagements/acme --out project # refuses until gates clear
212
+
213
+ fde scan engagements/acme # what this hardware runs
214
+ fde cost --requests-per-day 500000 --model-b 70 # dated fleet sizing
215
+
216
+ fde kb validate # parse and cross-link the registry
217
+ fde kb gaps --root framework # what the corpus is missing
218
+ fde kb sweep --root framework # profiles no approach can serve
210
219
  .venv/bin/pytest -q
211
220
  ```
212
221
 
@@ -239,6 +248,66 @@ project/
239
248
  └── COMPLIANCE.md # jurisdiction obligations, when a locale pack was applied
240
249
  ```
241
250
 
251
+ ## The full lifecycle, copy-paste
252
+
253
+ Everything below runs from an empty directory after `pip install fde-framework`:
254
+
255
+ ```bash
256
+ fde start acme --statement "Extract fields from scanned supplier invoices; \
257
+ data cannot leave; 200,000 documents, 8,000 verified; a person is waiting."
258
+ # plays back the typed facts it read, and the three questions worth asking next
259
+
260
+ fde ask acme --role eval_owner # answer what discovery still needs
261
+ fde status acme # facts by scope, gates, % settled
262
+
263
+ cat > baseline.yaml <<'YAML'
264
+ volume: {value: 20000, unit: docs/month, definition: invoices received by AP}
265
+ cycle_time_per_unit_seconds: {value: 300, unit: s, definition: arrival to posted}
266
+ labour_hours_per_week: {value: 35, unit: h/week, definition: AP team keying time}
267
+ rework_rate: {value: 0.1, unit: ratio, definition: entries corrected after post}
268
+ exception_rate: {value: 0.07, unit: ratio, definition: routed to a human queue}
269
+ error_rate: {value: 0.04, unit: ratio, definition: wrong amount or vendor posted}
270
+ business_metric: {value: 9, unit: days, definition: mean days payable outstanding}
271
+ sampled: {n: 40, method: random invoices across two months}
272
+ YAML
273
+ fde baseline acme --file baseline.yaml
274
+ fde data-access acme --note "read replica returned 14 real rows"
275
+ fde security-review acme --note "client infosec walked the data paths"
276
+ fde ask acme --role eval_owner # or: fde waive acme client_readiness --reason "..."
277
+
278
+ fde build acme --out project # refuses until the gates truly pass
279
+ python project/evals/harness.py # red until implemented -- that's the exam
280
+ fde implement project # drive a coding agent until it's green
281
+ ```
282
+
283
+ ## Python API
284
+
285
+ The CLI is a thin layer; everything is importable. The registry loads from the
286
+ installed wheel, so this runs anywhere:
287
+
288
+ ```python
289
+ from fde.architect import architect
290
+ from fde.intake.prose import parse_prose
291
+ from fde.models.profile import Profile
292
+ from fde.registry import default_root, load_registry
293
+
294
+ registry = load_registry(default_root())
295
+
296
+ profile = Profile()
297
+ profile.ingest(parse_prose(
298
+ "500,000 scanned invoices; data cannot leave; 10,000 verified; "
299
+ "a person is waiting; structured records out.", registry))
300
+
301
+ design = architect(profile, registry)
302
+ print(design.topology) # customer-vpc
303
+ for component, decision in sorted(design.decisions.items()):
304
+ if decision.approach:
305
+ print(component, decision.approach, decision.rationale)
306
+ ```
307
+
308
+ Every decision object carries its rationale and its rejected alternatives —
309
+ the same receipts the emitted `ARCHITECTURE.md` prints.
310
+
242
311
  ## Common commands
243
312
 
244
313
  | | |
@@ -392,7 +461,7 @@ The registry is the shared asset; engagements are private working state.
392
461
  deliberately waits for a corpus of measured retrospectives rather than
393
462
  pretending a handful is evidence.
394
463
  - **More locale packs and stacks** — both are data; contributions enter
395
- against [CONTRIBUTING.md](CONTRIBUTING.md)'s contract.
464
+ against [CONTRIBUTING.md](CONTRIBUTING.md)'s contract (and the [code of conduct](CODE_OF_CONDUCT.md)).
396
465
  - **Language, channel, and device axes** — six of twenty industry test
397
466
  statements named regional languages, low bandwidth, or basic devices;
398
467
  the honest wiring (per-language evaluation, SMS/IVR serving approaches,
@@ -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.1"
5
+ version = "0.1.2"
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"}
@@ -47,7 +47,7 @@ from fde.models.fact import Fact
47
47
  from fde.models.profile import Profile
48
48
  from fde.models.respondent import Respondent, Role
49
49
  from fde.predicate import PredicateError, holds
50
- from fde.registry import KINDS, RegistryError, is_empty, load_registry
50
+ from fde.registry import KINDS, RegistryError, default_root, is_empty, load_registry
51
51
  from fde.scan import (
52
52
  GPU,
53
53
  Hardware,
@@ -60,32 +60,30 @@ from fde.scan import (
60
60
  from fde.space import Contradiction, Space
61
61
 
62
62
  app = typer.Typer(help="Take an engagement from problem statement to a runnable project.")
63
- kb = typer.Typer(help="Inspect the knowledge base in framework/.")
64
- app.add_typer(kb, name="kb")
65
-
66
- def _default_registry_root() -> Path:
67
- """A checkout's ./framework when present, else the copy in the wheel.
68
63
 
69
- Local first, always: a contributor editing the corpus must see their
70
- edits, not the packaged snapshot. The packaged copy is what makes
71
- `pip install fde-framework` a working tool rather than a tool with no
72
- knowledge base.
73
- """
74
- local = Path("framework")
75
- if local.is_dir():
76
- return local
77
- try:
78
- from importlib.resources import files
79
64
 
80
- packaged = Path(str(files("fde") / "framework"))
81
- if packaged.is_dir():
82
- return packaged
83
- except (ImportError, TypeError):
84
- pass
85
- return local
65
+ @app.callback(invoke_without_command=True)
66
+ def _main(
67
+ ctx: typer.Context,
68
+ version: Annotated[bool, typer.Option(
69
+ "--version", help="Print the version and exit."
70
+ )] = False,
71
+ ) -> None:
72
+ if version:
73
+ from importlib.metadata import version as pkg_version
86
74
 
75
+ try:
76
+ typer.echo(f"fde-framework {pkg_version('fde-framework')}")
77
+ except Exception: # noqa: BLE001 - source checkout without install metadata
78
+ typer.echo("fde-framework (source checkout)")
79
+ raise typer.Exit(0)
80
+ if ctx.invoked_subcommand is None:
81
+ typer.echo(ctx.get_help())
82
+ raise typer.Exit(0)
83
+ kb = typer.Typer(help="Inspect the knowledge base in framework/.")
84
+ app.add_typer(kb, name="kb")
87
85
 
88
- DEFAULT_ROOT = _default_registry_root()
86
+ DEFAULT_ROOT = default_root()
89
87
 
90
88
  # What `fde retro` writes, and the only shape ingest will treat as a filename.
91
89
  CASE_ID = re.compile(r"case-[0-9a-f]{6,32}")
@@ -93,7 +91,10 @@ CASE_ID = re.compile(r"case-[0-9a-f]{6,32}")
93
91
 
94
92
  @kb.command("validate")
95
93
  def kb_validate(
96
- root: Annotated[Path, typer.Option(help="Registry directory.")] = DEFAULT_ROOT,
94
+ root: Annotated[Path, typer.Option(
95
+ "--registry", "--root",
96
+ help="Registry directory (--root kept as an alias).",
97
+ )] = DEFAULT_ROOT,
97
98
  lenient: Annotated[
98
99
  bool, typer.Option(help="Report dangling links without failing.")
99
100
  ] = False,
@@ -145,6 +146,30 @@ def start(
145
146
  typer.echo(" artifacts/ drop specs, schemas and sample pairs here")
146
147
  if not statement:
147
148
  typer.echo("\nNo statement yet. Add prose later, or start answering questions.")
149
+ return
150
+
151
+ # The statement is prose; read it like prose. Handing the tool a
152
+ # sentence and being told "no facts recorded yet" one command later
153
+ # made the first minute feel broken -- the reader was just never asked.
154
+ try:
155
+ registry = load_registry(DEFAULT_ROOT)
156
+ except RegistryError:
157
+ return
158
+ facts = parse_prose(statement, registry, source="statement")
159
+ if facts:
160
+ typer.echo("")
161
+ typer.echo(restate(facts, registry))
162
+ session_id = _next_session_id(engagement, "frame")
163
+ briefs = engagement.artifacts_dir / "briefs"
164
+ briefs.mkdir(parents=True, exist_ok=True)
165
+ (briefs / f"{session_id}.txt").write_text(statement)
166
+ engagement.append(
167
+ Session(
168
+ session_id=session_id,
169
+ respondent=Respondent(role=Role.SYSTEM),
170
+ facts=facts,
171
+ )
172
+ )
148
173
 
149
174
 
150
175
  @app.command("status")
@@ -642,9 +667,13 @@ def _put(dimension, question):
642
667
  End of input ends the interview rather than aborting it: whatever was
643
668
  gathered up to that point is still worth recording.
644
669
  """
670
+ hint = ""
671
+ if getattr(dimension, "values", None):
672
+ hint = f" [{' / '.join(dimension.values)}]"
645
673
  while True:
646
674
  try:
647
- reply = typer.prompt(f"\n{question.asks}", default="", show_default=False)
675
+ reply = typer.prompt(f"\n{question.asks}{hint}", default="",
676
+ show_default=False)
648
677
  except (EOFError, typer.Abort):
649
678
  return None
650
679
  answer = parse_answer(dimension, reply)
@@ -715,6 +744,12 @@ def _engagement(root: Path):
715
744
  file is a one-line explanation, never a traceback -- a stack trace at a
716
745
  client site reads as the tool being broken rather than the input.
717
746
  """
747
+ root = Path(root)
748
+ # `fde ask acme` after `fde start acme` should just work: start put it
749
+ # in engagements/acme, and making every later command spell that out
750
+ # cost every new user their first two minutes.
751
+ if not (root / "facts").is_dir() and (Path("engagements") / root).is_dir():
752
+ root = Path("engagements") / root
718
753
  try:
719
754
  engagement = load_engagement(root)
720
755
  # Read once here so a hand-edited gates.yaml fails as a sentence
@@ -1670,7 +1705,7 @@ def implement_cmd(
1670
1705
  reported if the agent touches them. Every round lands in
1671
1706
  ops/implement-log.md.
1672
1707
  """
1673
- from fde.implement import run_loop
1708
+ from fde.implement import AgentMissing, run_loop
1674
1709
 
1675
1710
  project = Path(project)
1676
1711
  if not (project / "evals").is_dir() or not (project / "app").is_dir():
@@ -1680,8 +1715,12 @@ def implement_cmd(
1680
1715
  )
1681
1716
  raise typer.Exit(1)
1682
1717
 
1683
- report = run_loop(project, agent_cmd=agent_cmd, max_rounds=max_rounds,
1684
- check=check, holdout=holdout)
1718
+ try:
1719
+ report = run_loop(project, agent_cmd=agent_cmd, max_rounds=max_rounds,
1720
+ check=check, holdout=holdout)
1721
+ except AgentMissing as exc:
1722
+ typer.echo(str(exc), err=True)
1723
+ raise typer.Exit(1) from exc
1685
1724
  (project / "ops").mkdir(exist_ok=True)
1686
1725
  (project / "ops" / "implement-log.md").write_text(report.log())
1687
1726
 
@@ -1811,6 +1850,13 @@ def cost_cmd(
1811
1850
  typer.echo(f"arrival_rate from the engagement: {requests_per_day:,}/day")
1812
1851
  if human_waiting is None and values.get("human_waiting") is not None:
1813
1852
  human_waiting = values["human_waiting"] != "no"
1853
+ if requests_per_day is None and price_per_seat is not None:
1854
+ # Seat economics needs workflows, not fleet volume: the README's own
1855
+ # one-liner failed for demanding a number the check never uses.
1856
+ requests_per_day = int(workflows_per_day)
1857
+ skip_fleet = True
1858
+ else:
1859
+ skip_fleet = False
1814
1860
  if requests_per_day is None:
1815
1861
  typer.echo(
1816
1862
  "no volume to size for -- pass --requests-per-day, or --root an "
@@ -1820,31 +1866,35 @@ def cost_cmd(
1820
1866
  if human_waiting is None:
1821
1867
  human_waiting = True
1822
1868
 
1823
- plan = size_for(requests_per_day, params_b, today=stamp)
1824
- comparison = compare_hosting(
1825
- requests_per_day, params_b, human_waiting=human_waiting, today=stamp
1826
- )
1869
+ if skip_fleet:
1870
+ plan = None
1871
+ else:
1872
+ plan = size_for(requests_per_day, params_b, today=stamp)
1873
+ if plan is not None:
1874
+ comparison = compare_hosting(
1875
+ requests_per_day, params_b, human_waiting=human_waiting, today=stamp
1876
+ )
1827
1877
 
1828
- typer.echo(
1829
- f"{params_b:g}B at {requests_per_day:,}/day"
1830
- f"{' (interactive)' if human_waiting else ' (batch, nobody waiting)'}\n"
1831
- )
1832
- typer.echo(f" naive: {plan['naive_replicas']} replica(s)")
1833
- typer.echo(
1834
- f" real: {plan['replicas']} replica(s) x {plan['gpus_per_replica']} "
1835
- f"card(s) = {plan['gpus']} cards"
1836
- )
1837
- for name, why in plan["factors"].items():
1838
- typer.echo(f" {name}: {why}")
1878
+ typer.echo(
1879
+ f"{params_b:g}B at {requests_per_day:,}/day"
1880
+ f"{' (interactive)' if human_waiting else ' (batch, nobody waiting)'}\n"
1881
+ )
1882
+ typer.echo(f" naive: {plan['naive_replicas']} replica(s)")
1883
+ typer.echo(
1884
+ f" real: {plan['replicas']} replica(s) x {plan['gpus_per_replica']} "
1885
+ f"card(s) = {plan['gpus']} cards"
1886
+ )
1887
+ for name, why in plan["factors"].items():
1888
+ typer.echo(f" {name}: {why}")
1839
1889
 
1840
- typer.echo(
1841
- f"\n self-hosted ${comparison['self_hosted_monthly']:,.0f}/mo\n"
1842
- f" managed ${comparison['managed_monthly']:,.0f}/mo\n"
1843
- f" -> {comparison['recommendation']}: {comparison['why']}"
1844
- )
1845
- typer.echo(
1846
- f"\n as of {plan['as_of']} -- {plan['rederive']}"
1847
- )
1890
+ typer.echo(
1891
+ f"\n self-hosted ${comparison['self_hosted_monthly']:,.0f}/mo\n"
1892
+ f" managed ${comparison['managed_monthly']:,.0f}/mo\n"
1893
+ f" -> {comparison['recommendation']}: {comparison['why']}"
1894
+ )
1895
+ typer.echo(
1896
+ f"\n as of {plan['as_of']} -- {plan['rederive']}"
1897
+ )
1848
1898
 
1849
1899
  if price_per_seat is not None:
1850
1900
  from fde.costing import unit_economics
@@ -1872,7 +1922,10 @@ def cost_cmd(
1872
1922
  @kb.command("ingest-case")
1873
1923
  def kb_ingest_case(
1874
1924
  case_file: Annotated[Path, typer.Argument(help="A case.json from `fde retro`.")],
1875
- root: Annotated[Path, typer.Option(help="Registry directory.")] = DEFAULT_ROOT,
1925
+ root: Annotated[Path, typer.Option(
1926
+ "--registry", "--root",
1927
+ help="Registry directory (--root kept as an alias).",
1928
+ )] = DEFAULT_ROOT,
1876
1929
  ) -> None:
1877
1930
  """Bring a captured case into the corpus -- as pending, never as reviewed.
1878
1931
 
@@ -2048,7 +2101,10 @@ def kb_suggest(
2048
2101
 
2049
2102
  @kb.command("sweep")
2050
2103
  def kb_sweep(
2051
- root: Annotated[Path, typer.Option(help="Registry directory.")] = DEFAULT_ROOT,
2104
+ root: Annotated[Path, typer.Option(
2105
+ "--registry", "--root",
2106
+ help="Registry directory (--root kept as an alias).",
2107
+ )] = DEFAULT_ROOT,
2052
2108
  samples: Annotated[int, typer.Option(help="Fully specified profiles to try.")] = 300,
2053
2109
  seed: Annotated[int, typer.Option(help="Deterministic sampling seed.")] = 0,
2054
2110
  ) -> None:
@@ -2086,7 +2142,10 @@ def kb_sweep(
2086
2142
 
2087
2143
  @kb.command("gaps")
2088
2144
  def kb_gaps(
2089
- root: Annotated[Path, typer.Option(help="Registry directory.")] = DEFAULT_ROOT,
2145
+ root: Annotated[Path, typer.Option(
2146
+ "--registry", "--root",
2147
+ help="Registry directory (--root kept as an alias).",
2148
+ )] = DEFAULT_ROOT,
2090
2149
  ) -> None:
2091
2150
  """Report what the corpus is missing. Work items, not errors -- always exits 0."""
2092
2151
  try:
@@ -216,6 +216,40 @@ def _write_package(architecture: Architecture, out: Path) -> None:
216
216
  f"Regenerating from the same profile produces the same files, so a diff\n"
217
217
  f'between two builds means a decision changed.\n"""\n'
218
218
  )
219
+ (out / "README.md").write_text(
220
+ f"# {architecture.topology} deployment -- generated by fde-framework\n"
221
+ "\n"
222
+ "This project was emitted from an engagement profile. Every decision\n"
223
+ "in it is traced in [ARCHITECTURE.md](ARCHITECTURE.md); every risk\n"
224
+ "accepted along the way is in [RISKS.md](RISKS.md).\n"
225
+ "\n"
226
+ "## Run the evaluation\n"
227
+ "\n"
228
+ "```bash\n"
229
+ "python evals/harness.py --min-score 0.9\n"
230
+ "```\n"
231
+ "\n"
232
+ "It fails until the pipeline is implemented end to end -- that is the\n"
233
+ "point. The scaffolded components under `app/components/` carry their\n"
234
+ "contracts; implement them by hand, or drive a coding agent against\n"
235
+ "the harness with `fde implement .` (the evals, the boundary and the\n"
236
+ "decision documents are fenced -- an agent that edits them is caught\n"
237
+ "and reverted).\n"
238
+ "\n"
239
+ "## The pieces\n"
240
+ "\n"
241
+ "| Path | What it is |\n"
242
+ "|---|---|\n"
243
+ "| `app/pipeline.py` | The run order: payload steps, approval gates, critics |\n"
244
+ "| `app/contract.py` | RefusedInput -- forbidden input is refused, never guessed at |\n"
245
+ "| `evals/` | Golden / edge / adversarial sets from the client's "
246
+ "own pairs, plus the acceptance protocol |\n"
247
+ "| `deploy/` | The substrate this profile earned, and how to tear it down |\n"
248
+ "| `ops/` | Runbook keyed to the failure taxonomy, SLOs, rollback |\n"
249
+ "\n"
250
+ "Regenerating from the same facts reproduces this project byte for\n"
251
+ "byte; a diff between two builds means a decision changed.\n"
252
+ )
219
253
  (out / "app" / "components" / "__init__.py").write_text("")
220
254
  (out / "app" / "contract.py").write_text(
221
255
  '"""The one contract every step shares: forbidden input is refused.\n'