datacharter 0.3.0__tar.gz → 0.3.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 (234) hide show
  1. {datacharter-0.3.0 → datacharter-0.3.2}/CHANGELOG.md +17 -0
  2. {datacharter-0.3.0 → datacharter-0.3.2}/PKG-INFO +51 -39
  3. datacharter-0.3.2/README.md +115 -0
  4. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/__init__.py +1 -1
  5. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/contracts/loader.py +4 -2
  6. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/app.py +9 -26
  7. datacharter-0.3.2/src/datacharter/server/security.py +56 -0
  8. {datacharter-0.3.0 → datacharter-0.3.2}/tests/contracts/test_loader.py +18 -0
  9. datacharter-0.3.2/tests/server/test_security.py +52 -0
  10. {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_cli_init.py +23 -0
  11. datacharter-0.3.0/README.md +0 -103
  12. {datacharter-0.3.0 → datacharter-0.3.2}/.github/workflows/banned-terms.yml +0 -0
  13. {datacharter-0.3.0 → datacharter-0.3.2}/.github/workflows/release.yml +0 -0
  14. {datacharter-0.3.0 → datacharter-0.3.2}/.github/workflows/test.yml +0 -0
  15. {datacharter-0.3.0 → datacharter-0.3.2}/.gitignore +0 -0
  16. {datacharter-0.3.0 → datacharter-0.3.2}/CODE_OF_CONDUCT.md +0 -0
  17. {datacharter-0.3.0 → datacharter-0.3.2}/CONTRIBUTING.md +0 -0
  18. {datacharter-0.3.0 → datacharter-0.3.2}/LICENSE +0 -0
  19. {datacharter-0.3.0 → datacharter-0.3.2}/SECURITY.md +0 -0
  20. {datacharter-0.3.0 → datacharter-0.3.2}/THIRD-PARTY-NOTICES.md +0 -0
  21. {datacharter-0.3.0 → datacharter-0.3.2}/TRADEMARKS.md +0 -0
  22. {datacharter-0.3.0 → datacharter-0.3.2}/brand/README.md +0 -0
  23. {datacharter-0.3.0 → datacharter-0.3.2}/brand/demo.gif +0 -0
  24. {datacharter-0.3.0 → datacharter-0.3.2}/brand/hero.png +0 -0
  25. {datacharter-0.3.0 → datacharter-0.3.2}/brand/mark.svg +0 -0
  26. {datacharter-0.3.0 → datacharter-0.3.2}/brand/screenshot-m4-agent.png +0 -0
  27. {datacharter-0.3.0 → datacharter-0.3.2}/brand/screenshot-m5-complete.png +0 -0
  28. {datacharter-0.3.0 → datacharter-0.3.2}/brand/screenshot-m5-core.png +0 -0
  29. {datacharter-0.3.0 → datacharter-0.3.2}/brand/screenshot-m5-features.png +0 -0
  30. {datacharter-0.3.0 → datacharter-0.3.2}/compose.yaml +0 -0
  31. {datacharter-0.3.0 → datacharter-0.3.2}/docs/_config.yml +0 -0
  32. {datacharter-0.3.0 → datacharter-0.3.2}/docs/about.md +0 -0
  33. {datacharter-0.3.0 → datacharter-0.3.2}/docs/agent.md +0 -0
  34. {datacharter-0.3.0 → datacharter-0.3.2}/docs/assets/add-source.gif +0 -0
  35. {datacharter-0.3.0 → datacharter-0.3.2}/docs/assets/drag-drop.gif +0 -0
  36. {datacharter-0.3.0 → datacharter-0.3.2}/docs/assets/hero.png +0 -0
  37. {datacharter-0.3.0 → datacharter-0.3.2}/docs/assets/screenshot.png +0 -0
  38. {datacharter-0.3.0 → datacharter-0.3.2}/docs/assets/snapshot.gif +0 -0
  39. {datacharter-0.3.0 → datacharter-0.3.2}/docs/charter-yaml.md +0 -0
  40. {datacharter-0.3.0 → datacharter-0.3.2}/docs/cli.md +0 -0
  41. {datacharter-0.3.0 → datacharter-0.3.2}/docs/faq.md +0 -0
  42. {datacharter-0.3.0 → datacharter-0.3.2}/docs/index.html +0 -0
  43. {datacharter-0.3.0 → datacharter-0.3.2}/docs/mcp.md +0 -0
  44. {datacharter-0.3.0 → datacharter-0.3.2}/docs/quickstart.md +0 -0
  45. {datacharter-0.3.0 → datacharter-0.3.2}/docs/security.md +0 -0
  46. {datacharter-0.3.0 → datacharter-0.3.2}/docs/sources.md +0 -0
  47. {datacharter-0.3.0 → datacharter-0.3.2}/docs/workspace.md +0 -0
  48. {datacharter-0.3.0 → datacharter-0.3.2}/pyproject.toml +0 -0
  49. {datacharter-0.3.0 → datacharter-0.3.2}/scripts/build_ui.sh +0 -0
  50. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/agent/__init__.py +0 -0
  51. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/agent/cache.py +0 -0
  52. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/agent/llm.py +0 -0
  53. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/agent/loop.py +0 -0
  54. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/agent/tools.py +0 -0
  55. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/cli.py +0 -0
  56. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/contracts/__init__.py +0 -0
  57. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/contracts/metrics.py +0 -0
  58. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/contracts/pii.py +0 -0
  59. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/contracts/resolve.py +0 -0
  60. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/contracts/secrets.py +0 -0
  61. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/contracts/writer.py +0 -0
  62. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/__init__.py +0 -0
  63. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/aggregate.py +0 -0
  64. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/guard.py +0 -0
  65. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/provenance.py +0 -0
  66. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/pushdown.py +0 -0
  67. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/scrub.py +0 -0
  68. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/session.py +0 -0
  69. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/snowflake.py +0 -0
  70. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/sources.py +0 -0
  71. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/statekey.py +0 -0
  72. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/mcp/__init__.py +0 -0
  73. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/mcp/server.py +0 -0
  74. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/models.py +0 -0
  75. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/__init__.py +0 -0
  76. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/llm_admin.py +0 -0
  77. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/source_admin.py +0 -0
  78. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/abap-DLDM7-KI.js +0 -0
  79. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/apex-DNDY2TF8.js +0 -0
  80. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/azcli-Y6nb8tq_.js +0 -0
  81. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/bat-BwHxbl9M.js +0 -0
  82. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/bicep-CFznDFnq.js +0 -0
  83. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/cameligo-Bf6VGUru.js +0 -0
  84. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/clojure-Dnu-v4kV.js +0 -0
  85. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/codicon-Brq4_Ui5.ttf +0 -0
  86. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/coffee-Bd8akH9Z.js +0 -0
  87. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/cpp-BbWJElDN.js +0 -0
  88. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/csharp-Co3qMtFm.js +0 -0
  89. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/csp-D-4FJmMZ.js +0 -0
  90. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/css-DdJfP1eB.js +0 -0
  91. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/css.worker-CqwWTjeG.js +0 -0
  92. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/cssMode-BuAVOLr4.js +0 -0
  93. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/cypher-cTPe9QuQ.js +0 -0
  94. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/dart-BOtBlQCF.js +0 -0
  95. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/dockerfile-BG73LgW2.js +0 -0
  96. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/ecl-BEgZUVRK.js +0 -0
  97. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/editor.worker-DCKwvLbM.js +0 -0
  98. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/elixir-BkW5O-1t.js +0 -0
  99. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/flow9-BeJ5waoc.js +0 -0
  100. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/freemarker2-DM0thzwF.js +0 -0
  101. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/fsharp-PahG7c26.js +0 -0
  102. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/go-acbASCJo.js +0 -0
  103. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/graphql-BxJiqAUM.js +0 -0
  104. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/handlebars-BkMf3Qj4.js +0 -0
  105. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/hcl-DtV1sZF8.js +0 -0
  106. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/html-KeNy2AFd.js +0 -0
  107. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/html.worker-DhJ4VZMp.js +0 -0
  108. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/htmlMode-CN5rSB9p.js +0 -0
  109. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/index-CX4OWOHb.css +0 -0
  110. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/index-CuaSFI7T.js +0 -0
  111. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/ini-Kd9XrMLS.js +0 -0
  112. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/java-CXBNlu9o.js +0 -0
  113. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/javascript-CZu1iw4q.js +0 -0
  114. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/json.worker-BF9eV5mS.js +0 -0
  115. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/jsonMode-BtMXpE2Q.js +0 -0
  116. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/julia-cl7-CwDS.js +0 -0
  117. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/kotlin-s7OhZKlX.js +0 -0
  118. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/less-9HpZscsL.js +0 -0
  119. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/lexon-OrD6JF1K.js +0 -0
  120. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/liquid-mhqehfbE.js +0 -0
  121. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/lspLanguageFeatures-D1NXFkgg.js +0 -0
  122. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/lua-Cyyb5UIc.js +0 -0
  123. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/m3-B8OfTtLu.js +0 -0
  124. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/markdown-BFxVWTOG.js +0 -0
  125. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/mdx-69yOd9f1.js +0 -0
  126. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/mips-CiqrrVzr.js +0 -0
  127. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/monaco-CB2SckMT.css +0 -0
  128. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/monaco-DNze5gXB.js +0 -0
  129. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/msdax-DmeGPVcC.js +0 -0
  130. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/mysql-C_tMU-Nz.js +0 -0
  131. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/objective-c-BDtDVThU.js +0 -0
  132. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/pascal-vHIfCaH5.js +0 -0
  133. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/pascaligo-DtZ0uQbO.js +0 -0
  134. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/perl-Ub6l9XKa.js +0 -0
  135. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/pgsql-BlNEE0v7.js +0 -0
  136. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/php-BBUBE1dy.js +0 -0
  137. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/pla-DSh2-awV.js +0 -0
  138. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/postiats-CocnycG-.js +0 -0
  139. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/powerquery-tScXyioY.js +0 -0
  140. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/powershell-COWaemsV.js +0 -0
  141. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/protobuf-Brw8urJB.js +0 -0
  142. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/pug-8SOpv6rk.js +0 -0
  143. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/python-C5qpjWC4.js +0 -0
  144. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/qsharp-Bw9ernYp.js +0 -0
  145. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/r-j7ic8hl3.js +0 -0
  146. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/razor-fg0l3XC9.js +0 -0
  147. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/redis-Bu5POkcn.js +0 -0
  148. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/redshift-Bs9aos_-.js +0 -0
  149. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/restructuredtext-CqXO7rUv.js +0 -0
  150. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/ruby-zBfavPgS.js +0 -0
  151. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/rust-BzKRNQWT.js +0 -0
  152. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/sb-BBc9UKZt.js +0 -0
  153. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/scala-D9hQfWCl.js +0 -0
  154. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/scheme-BPhDTwHR.js +0 -0
  155. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/scss-CBJaRo0y.js +0 -0
  156. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/shell-DiJ1NA_G.js +0 -0
  157. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/solidity-Db0IVjzk.js +0 -0
  158. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/sophia-CnS9iZB_.js +0 -0
  159. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/sparql-CJmd_6j2.js +0 -0
  160. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/sql-ClhHkBeG.js +0 -0
  161. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/st-CHwy0fLd.js +0 -0
  162. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/swift-Bqt4WxQ4.js +0 -0
  163. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/systemverilog-Bs9z6M-B.js +0 -0
  164. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/tcl-Dm6ycUr_.js +0 -0
  165. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/ts.worker-DTZAwq0V.js +0 -0
  166. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/tsMode-CaXNdXrQ.js +0 -0
  167. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/twig-Csy3S7wG.js +0 -0
  168. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/typescript-CCbVPqil.js +0 -0
  169. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/typespec-Btyra-wh.js +0 -0
  170. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/vb-Db0cS2oM.js +0 -0
  171. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/wgsl-BTesnYfV.js +0 -0
  172. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/workers-CwEVRSMl.js +0 -0
  173. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/xml-CIpcyvha.js +0 -0
  174. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/yaml-CiikAWeE.js +0 -0
  175. {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/index.html +0 -0
  176. {datacharter-0.3.0 → datacharter-0.3.2}/tests/agent/test_agent.py +0 -0
  177. {datacharter-0.3.0 → datacharter-0.3.2}/tests/agent/test_cache.py +0 -0
  178. {datacharter-0.3.0 → datacharter-0.3.2}/tests/agent/test_llm_wire.py +0 -0
  179. {datacharter-0.3.0 → datacharter-0.3.2}/tests/contracts/test_metrics.py +0 -0
  180. {datacharter-0.3.0 → datacharter-0.3.2}/tests/contracts/test_pii.py +0 -0
  181. {datacharter-0.3.0 → datacharter-0.3.2}/tests/contracts/test_secrets_store.py +0 -0
  182. {datacharter-0.3.0 → datacharter-0.3.2}/tests/contracts/test_writer.py +0 -0
  183. {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_aggregate.py +0 -0
  184. {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_diff.py +0 -0
  185. {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_federation.py +0 -0
  186. {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_guard.py +0 -0
  187. {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_provenance.py +0 -0
  188. {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_pushdown.py +0 -0
  189. {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_scrub.py +0 -0
  190. {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_session.py +0 -0
  191. {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_source_lifecycle.py +0 -0
  192. {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_sources.py +0 -0
  193. {datacharter-0.3.0 → datacharter-0.3.2}/tests/integration/seed/mysql.sql +0 -0
  194. {datacharter-0.3.0 → datacharter-0.3.2}/tests/integration/seed/postgres.sql +0 -0
  195. {datacharter-0.3.0 → datacharter-0.3.2}/tests/integration/test_live_sources.py +0 -0
  196. {datacharter-0.3.0 → datacharter-0.3.2}/tests/mcp/test_mcp_server.py +0 -0
  197. {datacharter-0.3.0 → datacharter-0.3.2}/tests/server/test_api.py +0 -0
  198. {datacharter-0.3.0 → datacharter-0.3.2}/tests/server/test_llm_admin.py +0 -0
  199. {datacharter-0.3.0 → datacharter-0.3.2}/tests/server/test_offline.py +0 -0
  200. {datacharter-0.3.0 → datacharter-0.3.2}/tests/server/test_source_admin.py +0 -0
  201. {datacharter-0.3.0 → datacharter-0.3.2}/tests/server/test_sources_api.py +0 -0
  202. {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_agent_vidaimock_e2e.py +0 -0
  203. {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_drift.py +0 -0
  204. {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_explain.py +0 -0
  205. {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_packaging_e2e.py +0 -0
  206. {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_sample.py +0 -0
  207. {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_scaffold.py +0 -0
  208. {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_secrets_cli.py +0 -0
  209. {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_snapshot_recheck.py +0 -0
  210. {datacharter-0.3.0 → datacharter-0.3.2}/ui/index.html +0 -0
  211. {datacharter-0.3.0 → datacharter-0.3.2}/ui/package-lock.json +0 -0
  212. {datacharter-0.3.0 → datacharter-0.3.2}/ui/package.json +0 -0
  213. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/App.tsx +0 -0
  214. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/api.ts +0 -0
  215. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/ChartPanel.tsx +0 -0
  216. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/ChatPanel.tsx +0 -0
  217. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/HelpModal.tsx +0 -0
  218. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/LLMConfig.tsx +0 -0
  219. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/QueryFiles.tsx +0 -0
  220. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/ResultsGrid.tsx +0 -0
  221. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/SourceForm.tsx +0 -0
  222. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/SourceTree.tsx +0 -0
  223. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/SourcesView.tsx +0 -0
  224. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/Tutorial.tsx +0 -0
  225. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/lib/caption.ts +0 -0
  226. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/lib/chartSpec.ts +0 -0
  227. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/lib/columns.ts +0 -0
  228. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/lib/useResize.ts +0 -0
  229. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/main.tsx +0 -0
  230. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/monaco.ts +0 -0
  231. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/styles.css +0 -0
  232. {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/vite-env.d.ts +0 -0
  233. {datacharter-0.3.0 → datacharter-0.3.2}/ui/tsconfig.json +0 -0
  234. {datacharter-0.3.0 → datacharter-0.3.2}/ui/vite.config.ts +0 -0
@@ -5,6 +5,23 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.3.2] - 2026-07-24
9
+
10
+ ### Fixed
11
+
12
+ - `datacharter init` then `datacharter serve` failed with a `CharterError`: the
13
+ scaffolded charter uses an empty `sources: {}`, which the loader wrongly
14
+ rejected as "must be a non-empty mapping". A fresh workspace is now servable —
15
+ add sources afterward via `charter.yaml` or the in-app source manager.
16
+
17
+ ## [0.3.1] - 2026-07-24
18
+
19
+ ### Changed
20
+
21
+ - Internal: extracted the origin/host request guard (DC-SEC-006) from the
22
+ server into an importable `datacharter.server.security` module. No behavior
23
+ change; the existing anti-DNS-rebinding / cross-origin protections are intact.
24
+
8
25
  ## [0.3.0] - 2026-07-24
9
26
 
10
27
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: datacharter
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Charter your data — contract-governed local data exploration, powered by DuckDB
5
5
  Project-URL: Homepage, https://github.com/datacharter/datacharter
6
6
  Project-URL: Repository, https://github.com/datacharter/datacharter
@@ -59,6 +59,20 @@ profiling. Every answer shows which source columns it read; ask questions in
59
59
  plain language or serve the whole thing to an AI agent over [MCP](docs/mcp.md),
60
60
  with PII masked from the model — all on your machine.
61
61
 
62
+ ## What you can do
63
+
64
+ - **Drop a file, query it instantly.** Drag a CSV, Parquet, or JSON onto the window and run SQL on it right away — no import, no schema setup.
65
+ - **Join across sources — no pipelines.** Query and JOIN a Postgres table, a Parquet file, and a Snowflake table in a *single* SQL statement. No ETL, no copying everything into a warehouse first.
66
+ - **Connect all your data.** Postgres, MySQL, SQLite, SQL Server, BigQuery, Snowflake, files on S3/GCS/Azure, and Iceberg/Delta tables — all through one engine.
67
+ - **See answers as you type.** Live results preview while you write SQL, one-click auto-charts, and a profiling panel (missing values, distributions, outliers) — no separate BI tool.
68
+ - **Ask in plain English (optional).** Turn a question into SQL and an answer with the built-in agent — bring your own model, or run one fully local with no API key.
69
+ - **Keep sensitive data away from the AI.** Mark PII columns once; the agent and any connected AI see masked values (`•••`) while you still see the real data locally. Flip **Agent view** to see exactly what the model sees.
70
+ - **Safe by design.** The engine is read-only by construction — no query can write, delete, or touch the filesystem — so pointing an AI (or a teammate) at your real databases can't do damage.
71
+ - **Point AI tools at your data, safely.** A governed MCP server exposes read-only, PII-masked query tools to Cursor, Cline, or your own agent.
72
+ - **Trust every answer.** Each result shows exactly which source columns it read — so you always know where a number came from.
73
+ - **Save, reuse, export.** Snapshot a result as a reusable local table; export to CSV, Parquet, JSON, or XLSX.
74
+ - **Governance you can automate.** Catch schema/PII drift in CI, auto-detect PII columns, diff data across sources, and define certified metrics — from the command line.
75
+
62
76
  ![DataCharter — live SQL preview, auto-charts, per-query provenance, and PII masking](https://raw.githubusercontent.com/datacharter/datacharter/main/brand/demo.gif)
63
77
 
64
78
  **Status: pre-release.** V1 in development.
@@ -66,59 +80,57 @@ with PII masked from the model — all on your machine.
66
80
  ## Quick start
67
81
 
68
82
  ```sh
69
- # Try it instantly on generated demo data — no config, nothing to install globally:
70
- uvx datacharter serve
71
- # → opens a local workspace on http://127.0.0.1:8321
83
+ # Try it instantly on generated demo data — no install, no config:
84
+ uvx datacharter serve # needs `uv` → https://astral.sh/uv
85
+ # → serves at http://127.0.0.1:8321 (open it in your browser)
72
86
 
73
87
  # Or install it:
74
- pip install datacharter
88
+ pip install datacharter # Python 3.11+
89
+
90
+ # Start your own workspace:
91
+ datacharter init # scaffolds charter.yaml, queries/, .env.example
92
+ # → add a source: edit charter.yaml, or use the "Sources" panel in the UI
93
+ datacharter serve # → http://127.0.0.1:8321
94
+ ```
95
+
96
+ Then, once it's running, **drag a CSV, Parquet, or JSON file onto the window** to
97
+ query it instantly — no config needed.
75
98
 
76
- # Start a real workspace:
77
- datacharter init # scaffolds charter.yaml, queries/, .env.example
78
- datacharter serve # explore in your browser
99
+ **Optional natural-language agent** point it at any OpenAI-compatible endpoint:
79
100
 
80
- # Natural-language agent — bring your own endpoint…
81
- export OPENAI_BASE_URL=... # any OpenAI-compatible API
101
+ ```sh
102
+ export OPENAI_BASE_URL=... # any OpenAI-compatible API
82
103
  export OPENAI_API_KEY=...
83
104
  datacharter serve
84
- # …or run fully local (no API key, no data leaves your machine):
85
- datacharter serve --local # uses Ollama (qwen3:8b by default)
86
105
  ```
87
106
 
88
- Drop a CSV, Parquet, or JSON file onto the window to query it instantly.
107
+ …or run **fully local** no API key, no data leaves your machine (requires
108
+ [Ollama](https://ollama.com)):
89
109
 
90
- ## Why
110
+ ```sh
111
+ ollama pull qwen3:8b # once
112
+ datacharter serve --local # qwen3:8b by default (--model to change)
113
+ ```
114
+
115
+ ## Why DataCharter
91
116
 
92
117
  - **Your contracts are the catalog.** `charter.yaml` describes sources, tables,
93
- and PII fields — the same contract spec your data team already writes.
94
- - **One engine, every source.** Postgres, MySQL, SQLite, BigQuery, SQL Server,
95
- S3/GCS/Azure files, Iceberg, Delta federated joins across all of them, with
96
- filters and projections pushed down where the data lives — even across a
97
- cross-source join, each leg is filtered at its source. Snowflake is supported
98
- via connector extract (`datacharter[snowflake]`) with filters/projections
99
- pushed into the extract. Every source's tables are exposed under one flat
100
- `source__table` naming scheme.
101
- - **Local-first.** One process, your machine, no cloud dependency. Optional
102
- `--local` agent mode runs a small open model via Ollama — no API key, no data
103
- leaves your machine.
118
+ and PII fields — the same contract spec your data team already writes, so
119
+ there's no separate metadata store to maintain.
120
+ - **Real federation, not just a shared connection.** Filters and projections are
121
+ pushed down to each source — even across a cross-source join, every leg is
122
+ filtered where its data lives. (Snowflake runs via connector extract,
123
+ `datacharter[snowflake]`, with the same pushdown into the extract.)
124
+ - **Local-first.** One process, your machine, no cloud dependency. The optional
125
+ `--local` agent runs a small open model via Ollama — no API key, no data leaves
126
+ your machine.
104
127
  - **The workspace is a directory.** `charter.yaml` + `queries/*.sql` +
105
128
  `.env.example` — commit it, clone it, `datacharter serve`. Your team's whole
106
129
  exploration environment travels as a repo; secrets and local state never do.
107
130
 
108
- ## More than a viewer
109
-
110
- DataCharter governs and audits your data, not just displays it — see the
111
- [CLI reference](docs/cli.md) for the full command set:
112
-
113
- - **Governed MCP server** — `datacharter mcp` exposes read-only, PII-masked query
114
- tools to any MCP client (Cursor, Cline, or your own agent).
115
- - **Contracts you can check** — `datacharter drift` exits non-zero when a declared
116
- table or PII column disappears; `datacharter scan` detects PII columns to add.
117
- - **Answers that show their work** — every result reports the source columns it
118
- read; `datacharter diff` compares relations across sources; `datacharter metric`
119
- runs governed metric definitions.
120
- - **Privacy-first** — `serve --offline` runs with no outbound network, and the
121
- model never sees raw PII (flip **Agent view** in the UI to see exactly what it does).
131
+ DataCharter governs and audits your data, not just displays it. The full command
132
+ set (`drift`, `scan`, `diff`, `metric`, `mcp`, and more) is in the
133
+ [CLI reference](docs/cli.md); the security model is in [security](docs/security.md).
122
134
 
123
135
  ## Built on
124
136
 
@@ -0,0 +1,115 @@
1
+ # DataCharter
2
+
3
+ > Explore all your data locally, in one place — contract-governed data exploration, powered by DuckDB
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/datacharter)](https://pypi.org/project/datacharter/)
6
+ [![Python](https://img.shields.io/pypi/pyversions/datacharter)](https://pypi.org/project/datacharter/)
7
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue)](LICENSE)
8
+
9
+ Contract-governed local data exploration, **powered by [DuckDB](https://duckdb.org)**.
10
+ Define your sources as data contracts
11
+ ([ODCS](https://bitol-io.github.io/open-data-contract-standard/)-compatible YAML),
12
+ query them through DuckDB's SQL federation engine with real source pushdowns,
13
+ and explore in a local web UI — SQL editor with live preview, auto-charts,
14
+ profiling. Every answer shows which source columns it read; ask questions in
15
+ plain language or serve the whole thing to an AI agent over [MCP](docs/mcp.md),
16
+ with PII masked from the model — all on your machine.
17
+
18
+ ## What you can do
19
+
20
+ - **Drop a file, query it instantly.** Drag a CSV, Parquet, or JSON onto the window and run SQL on it right away — no import, no schema setup.
21
+ - **Join across sources — no pipelines.** Query and JOIN a Postgres table, a Parquet file, and a Snowflake table in a *single* SQL statement. No ETL, no copying everything into a warehouse first.
22
+ - **Connect all your data.** Postgres, MySQL, SQLite, SQL Server, BigQuery, Snowflake, files on S3/GCS/Azure, and Iceberg/Delta tables — all through one engine.
23
+ - **See answers as you type.** Live results preview while you write SQL, one-click auto-charts, and a profiling panel (missing values, distributions, outliers) — no separate BI tool.
24
+ - **Ask in plain English (optional).** Turn a question into SQL and an answer with the built-in agent — bring your own model, or run one fully local with no API key.
25
+ - **Keep sensitive data away from the AI.** Mark PII columns once; the agent and any connected AI see masked values (`•••`) while you still see the real data locally. Flip **Agent view** to see exactly what the model sees.
26
+ - **Safe by design.** The engine is read-only by construction — no query can write, delete, or touch the filesystem — so pointing an AI (or a teammate) at your real databases can't do damage.
27
+ - **Point AI tools at your data, safely.** A governed MCP server exposes read-only, PII-masked query tools to Cursor, Cline, or your own agent.
28
+ - **Trust every answer.** Each result shows exactly which source columns it read — so you always know where a number came from.
29
+ - **Save, reuse, export.** Snapshot a result as a reusable local table; export to CSV, Parquet, JSON, or XLSX.
30
+ - **Governance you can automate.** Catch schema/PII drift in CI, auto-detect PII columns, diff data across sources, and define certified metrics — from the command line.
31
+
32
+ ![DataCharter — live SQL preview, auto-charts, per-query provenance, and PII masking](https://raw.githubusercontent.com/datacharter/datacharter/main/brand/demo.gif)
33
+
34
+ **Status: pre-release.** V1 in development.
35
+
36
+ ## Quick start
37
+
38
+ ```sh
39
+ # Try it instantly on generated demo data — no install, no config:
40
+ uvx datacharter serve # needs `uv` → https://astral.sh/uv
41
+ # → serves at http://127.0.0.1:8321 (open it in your browser)
42
+
43
+ # Or install it:
44
+ pip install datacharter # Python 3.11+
45
+
46
+ # Start your own workspace:
47
+ datacharter init # scaffolds charter.yaml, queries/, .env.example
48
+ # → add a source: edit charter.yaml, or use the "Sources" panel in the UI
49
+ datacharter serve # → http://127.0.0.1:8321
50
+ ```
51
+
52
+ Then, once it's running, **drag a CSV, Parquet, or JSON file onto the window** to
53
+ query it instantly — no config needed.
54
+
55
+ **Optional natural-language agent** — point it at any OpenAI-compatible endpoint:
56
+
57
+ ```sh
58
+ export OPENAI_BASE_URL=... # any OpenAI-compatible API
59
+ export OPENAI_API_KEY=...
60
+ datacharter serve
61
+ ```
62
+
63
+ …or run **fully local** — no API key, no data leaves your machine (requires
64
+ [Ollama](https://ollama.com)):
65
+
66
+ ```sh
67
+ ollama pull qwen3:8b # once
68
+ datacharter serve --local # qwen3:8b by default (--model to change)
69
+ ```
70
+
71
+ ## Why DataCharter
72
+
73
+ - **Your contracts are the catalog.** `charter.yaml` describes sources, tables,
74
+ and PII fields — the same contract spec your data team already writes, so
75
+ there's no separate metadata store to maintain.
76
+ - **Real federation, not just a shared connection.** Filters and projections are
77
+ pushed down to each source — even across a cross-source join, every leg is
78
+ filtered where its data lives. (Snowflake runs via connector extract,
79
+ `datacharter[snowflake]`, with the same pushdown into the extract.)
80
+ - **Local-first.** One process, your machine, no cloud dependency. The optional
81
+ `--local` agent runs a small open model via Ollama — no API key, no data leaves
82
+ your machine.
83
+ - **The workspace is a directory.** `charter.yaml` + `queries/*.sql` +
84
+ `.env.example` — commit it, clone it, `datacharter serve`. Your team's whole
85
+ exploration environment travels as a repo; secrets and local state never do.
86
+
87
+ DataCharter governs and audits your data, not just displays it. The full command
88
+ set (`drift`, `scan`, `diff`, `metric`, `mcp`, and more) is in the
89
+ [CLI reference](docs/cli.md); the security model is in [security](docs/security.md).
90
+
91
+ ## Built on
92
+
93
+ DataCharter stands on excellent open-source foundations:
94
+
95
+ - **[DuckDB](https://duckdb.org)** — the analytical engine at our core:
96
+ federation (`ATTACH`), file formats, Iceberg/Delta, encryption, autocomplete.
97
+ - **[Open Data Contract Standard](https://bitol-io.github.io/open-data-contract-standard/)** /
98
+ [datacontract.com](https://datacontract-specification.com/) — the contract format `charter.yaml` speaks.
99
+ - **[Model Context Protocol](https://modelcontextprotocol.io)** — the open protocol
100
+ the `datacharter mcp` server speaks to agents and MCP clients.
101
+ - **[Vega-Lite](https://vega.github.io/vega-lite/)** — declarative charting.
102
+ - **[Monaco Editor](https://microsoft.github.io/monaco-editor/)** — the SQL editor.
103
+ - **[TanStack Table & Virtual](https://tanstack.com/)** — the virtualized results grid.
104
+ - And the Python & React ecosystems — FastAPI, pydantic, httpx, keyring, and
105
+ ruamel.yaml on the backend; React and Vite on the front.
106
+
107
+ Testing uses **[VidaiMock](https://github.com/vidaiUK/VidaiMock)**, an
108
+ Apache-2.0 mock LLM server, as the offline agent endpoint in CI.
109
+
110
+ DuckDB is a trademark of the DuckDB Foundation. DataCharter is an independent
111
+ project and is not affiliated with or endorsed by the DuckDB Foundation.
112
+
113
+ ## License
114
+
115
+ [Apache-2.0](LICENSE)
@@ -1,3 +1,3 @@
1
1
  """DataCharter — contract-governed local data exploration."""
2
2
 
3
- __version__ = "0.3.0"
3
+ __version__ = "0.3.2"
@@ -52,8 +52,10 @@ def load_charter(workspace: Path | str, filename: str = CHARTER_FILE) -> Charter
52
52
  f"{filename}: unsupported version {version!r}; expected {SUPPORTED_VERSION}."
53
53
  )
54
54
  sources_raw = raw.get("sources")
55
- if not isinstance(sources_raw, dict) or not sources_raw:
56
- raise CharterError(f"{filename}: 'sources' must be a non-empty mapping of name -> source.")
55
+ if sources_raw is None: # a fresh `datacharter init` workspace: add sources later
56
+ sources_raw = {}
57
+ if not isinstance(sources_raw, dict):
58
+ raise CharterError(f"{filename}: 'sources' must be a mapping of name -> source.")
57
59
 
58
60
  resolver = SecretResolver(workspace)
59
61
  warnings: list[str] = []
@@ -9,7 +9,6 @@ import uuid
9
9
  from collections.abc import AsyncIterator
10
10
  from contextlib import asynccontextmanager
11
11
  from pathlib import Path
12
- from urllib.parse import urlparse
13
12
 
14
13
  from fastapi import FastAPI, Request, UploadFile
15
14
  from fastapi.responses import FileResponse, JSONResponse, StreamingResponse
@@ -27,14 +26,11 @@ from datacharter.engine.guard import QueryNotAllowed
27
26
  from datacharter.engine.session import DEFAULT_ROW_LIMIT, Engine, EngineError, QueryTimeout
28
27
  from datacharter.engine.statekey import resolve_state_key
29
28
  from datacharter.models import QueryResult, Source, SourceType
30
- from datacharter.server import llm_admin, source_admin
29
+ from datacharter.server import llm_admin, security, source_admin
31
30
 
32
31
  HEARTBEAT_S = 1.0
33
32
  DEFAULT_TIMEOUT_S = 60.0
34
33
 
35
- # Hostnames that always denote this machine (anti-DNS-rebinding allowlist).
36
- _LOOPBACK_HOSTS = frozenset({"localhost", "127.0.0.1", "::1"})
37
- _ALL_INTERFACES = frozenset({"0.0.0.0", "::"})
38
34
  _MAX_UPLOAD_BYTES = 512 * 1024 * 1024 # 512 MB cap on a single upload
39
35
 
40
36
 
@@ -90,7 +86,7 @@ def create_app(
90
86
  """
91
87
  workspace = Path(workspace).resolve()
92
88
  loaded = charter if charter is not None else load_charter(workspace)
93
- allowed_hosts = _allowed_hosts(host)
89
+ allowed = security.allowed_hosts(host)
94
90
  state_key = _state_key()
95
91
 
96
92
  @asynccontextmanager
@@ -114,21 +110,15 @@ def create_app(
114
110
  @app.middleware("http")
115
111
  async def _origin_guard(request: Request, call_next):
116
112
  """Reject DNS-rebinding (bad Host) and cross-site browser requests (CSRF)."""
117
- host_name = (request.headers.get("host") or "").rsplit(":", 1)[0].strip("[]").lower()
118
- if allowed_hosts is not None and host_name and host_name not in allowed_hosts:
113
+ if not security.host_allowed(request, allowed):
119
114
  return _error(403, "forbidden_host", "Host not allowed.")
120
115
  path = request.url.path
121
- if path.startswith("/api/") and path != "/api/health":
122
- origin = request.headers.get("origin")
123
- if origin:
124
- origin_host = (urlparse(origin).hostname or "").lower()
125
- if origin_host not in (allowed_hosts or _LOOPBACK_HOSTS):
126
- return _error(403, "forbidden_origin", "Cross-origin request rejected.")
127
- if (request.headers.get("sec-fetch-site") or "").lower() in (
128
- "cross-site",
129
- "cross-origin",
130
- ):
131
- return _error(403, "forbidden_origin", "Cross-site request rejected.")
116
+ if (
117
+ path.startswith("/api/")
118
+ and path != "/api/health"
119
+ and not security.origin_allowed(request, allowed)
120
+ ):
121
+ return _error(403, "forbidden_origin", "Cross-origin request rejected.")
132
122
  return await call_next(request)
133
123
 
134
124
  @app.exception_handler(QueryNotAllowed)
@@ -364,13 +354,6 @@ def create_app(
364
354
  return app
365
355
 
366
356
 
367
- def _allowed_hosts(host: str) -> frozenset[str] | None:
368
- """Host-header allowlist for the bind address; None = all interfaces (skip)."""
369
- if host.lower() in _ALL_INTERFACES:
370
- return None # explicit network opt-in (D4) — Host allowlist can't apply
371
- return _LOOPBACK_HOSTS | {host.lower()}
372
-
373
-
374
357
  def _state_key() -> str | None:
375
358
  """Encryption key for the local state DB (D8) — see engine.statekey."""
376
359
  return resolve_state_key()
@@ -0,0 +1,56 @@
1
+ """Anti-DNS-rebinding / cross-origin request guards (DC-SEC-006).
2
+
3
+ Pure predicates shared by the core server and any HTTP transport built on the same
4
+ governed toolbox: they decide allow/deny from request headers, they do not build
5
+ responses. `host_allowed` blocks DNS-rebinding (a bad `Host`); `origin_allowed`
6
+ blocks cross-site browser requests (a foreign `Origin` / `Sec-Fetch-Site`).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from urllib.parse import urlparse
12
+
13
+ from starlette.requests import Request
14
+
15
+ __all__ = [
16
+ "LOOPBACK_HOSTS",
17
+ "ALL_INTERFACES",
18
+ "allowed_hosts",
19
+ "host_allowed",
20
+ "origin_allowed",
21
+ "is_request_allowed",
22
+ ]
23
+
24
+ # Hostnames that always denote this machine.
25
+ LOOPBACK_HOSTS = frozenset({"localhost", "127.0.0.1", "::1"})
26
+ ALL_INTERFACES = frozenset({"0.0.0.0", "::"})
27
+ _CROSS_SITE = frozenset({"cross-site", "cross-origin"})
28
+
29
+
30
+ def allowed_hosts(host: str) -> frozenset[str] | None:
31
+ """Host-header allowlist for a bind address; None = all interfaces (skip)."""
32
+ if host.lower() in ALL_INTERFACES:
33
+ return None # explicit network opt-in (D4) — a Host allowlist can't apply
34
+ return LOOPBACK_HOSTS | {host.lower()}
35
+
36
+
37
+ def host_allowed(request: Request, allowed: frozenset[str] | None) -> bool:
38
+ """False iff the `Host` header names a host outside the allowlist."""
39
+ host_name = (request.headers.get("host") or "").rsplit(":", 1)[0].strip("[]").lower()
40
+ return not (allowed is not None and host_name and host_name not in allowed)
41
+
42
+
43
+ def origin_allowed(request: Request, allowed: frozenset[str] | None) -> bool:
44
+ """False iff a cross-site `Origin` or `Sec-Fetch-Site` header is present."""
45
+ origin = request.headers.get("origin")
46
+ if origin:
47
+ origin_host = (urlparse(origin).hostname or "").lower()
48
+ if origin_host not in (allowed or LOOPBACK_HOSTS):
49
+ return False
50
+ return (request.headers.get("sec-fetch-site") or "").lower() not in _CROSS_SITE
51
+
52
+
53
+ def is_request_allowed(request: Request, allowed: frozenset[str] | None) -> bool:
54
+ """Both guards — for endpoints (e.g. MCP over HTTP) that want the full check on
55
+ every request, regardless of path."""
56
+ return host_allowed(request, allowed) and origin_allowed(request, allowed)
@@ -16,6 +16,24 @@ def test_minimal_file_source_loads(tmp_path):
16
16
  assert charter.warnings == []
17
17
 
18
18
 
19
+ def test_empty_sources_loads_as_fresh_workspace(tmp_path):
20
+ # `datacharter init` scaffolds `sources: {}`; a fresh workspace must be servable
21
+ # (sources are added later, via charter.yaml or the in-app source manager).
22
+ charter = load_charter(write_charter(tmp_path, "version: 1\nsources: {}\n"))
23
+ assert charter.sources == []
24
+
25
+
26
+ def test_null_sources_loads_as_empty(tmp_path):
27
+ charter = load_charter(write_charter(tmp_path, "version: 1\nsources:\n"))
28
+ assert charter.sources == []
29
+
30
+
31
+ def test_non_mapping_sources_rejected(tmp_path):
32
+ ws = write_charter(tmp_path, "version: 1\nsources:\n - a\n - b\n")
33
+ with pytest.raises(CharterError, match=r"'sources' must be a mapping"):
34
+ load_charter(ws)
35
+
36
+
19
37
  def test_env_reference_resolution(tmp_path, monkeypatch):
20
38
  monkeypatch.setenv("PG_PW", "resolved-pw-value")
21
39
  ws = write_charter(
@@ -0,0 +1,52 @@
1
+ from starlette.requests import Request
2
+
3
+ from datacharter.server.security import (
4
+ allowed_hosts,
5
+ host_allowed,
6
+ is_request_allowed,
7
+ origin_allowed,
8
+ )
9
+
10
+
11
+ def _req(headers: dict) -> Request:
12
+ raw = [(k.lower().encode(), v.encode()) for k, v in headers.items()]
13
+ return Request({"type": "http", "headers": raw})
14
+
15
+
16
+ def test_allowed_hosts_includes_loopback_and_bind():
17
+ ah = allowed_hosts("127.0.0.1")
18
+ assert ah is not None and "127.0.0.1" in ah and "localhost" in ah
19
+
20
+
21
+ def test_all_interfaces_disables_allowlist():
22
+ assert allowed_hosts("0.0.0.0") is None
23
+
24
+
25
+ def test_loopback_host_allowed():
26
+ assert host_allowed(_req({"host": "127.0.0.1:8765"}), allowed_hosts("127.0.0.1")) is True
27
+
28
+
29
+ def test_foreign_host_rejected():
30
+ assert host_allowed(_req({"host": "evil.example.com"}), allowed_hosts("127.0.0.1")) is False
31
+
32
+
33
+ def test_cross_origin_rejected():
34
+ ah = allowed_hosts("127.0.0.1")
35
+ assert origin_allowed(_req({"origin": "http://evil.example.com"}), ah) is False
36
+
37
+
38
+ def test_cross_site_secfetch_rejected():
39
+ ah = allowed_hosts("127.0.0.1")
40
+ assert origin_allowed(_req({"sec-fetch-site": "cross-site"}), ah) is False
41
+
42
+
43
+ def test_same_origin_allowed():
44
+ req = _req({"origin": "http://127.0.0.1", "sec-fetch-site": "same-origin"})
45
+ assert origin_allowed(req, allowed_hosts("127.0.0.1")) is True
46
+
47
+
48
+ def test_is_request_allowed_combines_host_and_origin():
49
+ ah = allowed_hosts("127.0.0.1")
50
+ assert is_request_allowed(_req({"host": "127.0.0.1:8765"}), ah) is True
51
+ bad = _req({"host": "127.0.0.1", "origin": "http://evil.example.com"})
52
+ assert is_request_allowed(bad, ah) is False
@@ -44,6 +44,29 @@ def test_demo_workspace_loads_and_queries_end_to_end(tmp_path):
44
44
  assert len(result.rows) == 2
45
45
 
46
46
 
47
+ def test_plain_init_charter_loads_as_empty(tmp_path):
48
+ # Regression: plain `init` scaffolds `sources: {}`; the scaffolded charter MUST
49
+ # load. 0.3.1 shipped an init template that `load_charter` then refused.
50
+ assert main(["init", str(tmp_path)]) == 0
51
+ charter = load_charter(tmp_path)
52
+ assert charter.sources == []
53
+
54
+
55
+ def test_plain_init_workspace_serves_end_to_end(tmp_path):
56
+ # The real user path `init` -> `serve`: create_app must boot on a fresh empty
57
+ # workspace and the sources API returns an empty list (sources added later).
58
+ from fastapi.testclient import TestClient
59
+
60
+ from datacharter.server import create_app
61
+
62
+ assert main(["init", str(tmp_path)]) == 0
63
+ app = create_app(tmp_path)
64
+ with TestClient(app, base_url="http://127.0.0.1") as c:
65
+ resp = c.get("/api/sources")
66
+ assert resp.status_code == 200
67
+ assert resp.json()["sources"] == []
68
+
69
+
47
70
  def test_serve_workspace_resolution_prefers_existing_charter(tmp_path):
48
71
  from datacharter.cli import _resolve_serve_workspace, main
49
72
 
@@ -1,103 +0,0 @@
1
- # DataCharter
2
-
3
- > Explore all your data locally, in one place — contract-governed data exploration, powered by DuckDB
4
-
5
- [![PyPI](https://img.shields.io/pypi/v/datacharter)](https://pypi.org/project/datacharter/)
6
- [![Python](https://img.shields.io/pypi/pyversions/datacharter)](https://pypi.org/project/datacharter/)
7
- [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue)](LICENSE)
8
-
9
- Contract-governed local data exploration, **powered by [DuckDB](https://duckdb.org)**.
10
- Define your sources as data contracts
11
- ([ODCS](https://bitol-io.github.io/open-data-contract-standard/)-compatible YAML),
12
- query them through DuckDB's SQL federation engine with real source pushdowns,
13
- and explore in a local web UI — SQL editor with live preview, auto-charts,
14
- profiling. Every answer shows which source columns it read; ask questions in
15
- plain language or serve the whole thing to an AI agent over [MCP](docs/mcp.md),
16
- with PII masked from the model — all on your machine.
17
-
18
- ![DataCharter — live SQL preview, auto-charts, per-query provenance, and PII masking](https://raw.githubusercontent.com/datacharter/datacharter/main/brand/demo.gif)
19
-
20
- **Status: pre-release.** V1 in development.
21
-
22
- ## Quick start
23
-
24
- ```sh
25
- # Try it instantly on generated demo data — no config, nothing to install globally:
26
- uvx datacharter serve
27
- # → opens a local workspace on http://127.0.0.1:8321
28
-
29
- # Or install it:
30
- pip install datacharter
31
-
32
- # Start a real workspace:
33
- datacharter init # scaffolds charter.yaml, queries/, .env.example
34
- datacharter serve # explore in your browser
35
-
36
- # Natural-language agent — bring your own endpoint…
37
- export OPENAI_BASE_URL=... # any OpenAI-compatible API
38
- export OPENAI_API_KEY=...
39
- datacharter serve
40
- # …or run fully local (no API key, no data leaves your machine):
41
- datacharter serve --local # uses Ollama (qwen3:8b by default)
42
- ```
43
-
44
- Drop a CSV, Parquet, or JSON file onto the window to query it instantly.
45
-
46
- ## Why
47
-
48
- - **Your contracts are the catalog.** `charter.yaml` describes sources, tables,
49
- and PII fields — the same contract spec your data team already writes.
50
- - **One engine, every source.** Postgres, MySQL, SQLite, BigQuery, SQL Server,
51
- S3/GCS/Azure files, Iceberg, Delta — federated joins across all of them, with
52
- filters and projections pushed down where the data lives — even across a
53
- cross-source join, each leg is filtered at its source. Snowflake is supported
54
- via connector extract (`datacharter[snowflake]`) with filters/projections
55
- pushed into the extract. Every source's tables are exposed under one flat
56
- `source__table` naming scheme.
57
- - **Local-first.** One process, your machine, no cloud dependency. Optional
58
- `--local` agent mode runs a small open model via Ollama — no API key, no data
59
- leaves your machine.
60
- - **The workspace is a directory.** `charter.yaml` + `queries/*.sql` +
61
- `.env.example` — commit it, clone it, `datacharter serve`. Your team's whole
62
- exploration environment travels as a repo; secrets and local state never do.
63
-
64
- ## More than a viewer
65
-
66
- DataCharter governs and audits your data, not just displays it — see the
67
- [CLI reference](docs/cli.md) for the full command set:
68
-
69
- - **Governed MCP server** — `datacharter mcp` exposes read-only, PII-masked query
70
- tools to any MCP client (Cursor, Cline, or your own agent).
71
- - **Contracts you can check** — `datacharter drift` exits non-zero when a declared
72
- table or PII column disappears; `datacharter scan` detects PII columns to add.
73
- - **Answers that show their work** — every result reports the source columns it
74
- read; `datacharter diff` compares relations across sources; `datacharter metric`
75
- runs governed metric definitions.
76
- - **Privacy-first** — `serve --offline` runs with no outbound network, and the
77
- model never sees raw PII (flip **Agent view** in the UI to see exactly what it does).
78
-
79
- ## Built on
80
-
81
- DataCharter stands on excellent open-source foundations:
82
-
83
- - **[DuckDB](https://duckdb.org)** — the analytical engine at our core:
84
- federation (`ATTACH`), file formats, Iceberg/Delta, encryption, autocomplete.
85
- - **[Open Data Contract Standard](https://bitol-io.github.io/open-data-contract-standard/)** /
86
- [datacontract.com](https://datacontract-specification.com/) — the contract format `charter.yaml` speaks.
87
- - **[Model Context Protocol](https://modelcontextprotocol.io)** — the open protocol
88
- the `datacharter mcp` server speaks to agents and MCP clients.
89
- - **[Vega-Lite](https://vega.github.io/vega-lite/)** — declarative charting.
90
- - **[Monaco Editor](https://microsoft.github.io/monaco-editor/)** — the SQL editor.
91
- - **[TanStack Table & Virtual](https://tanstack.com/)** — the virtualized results grid.
92
- - And the Python & React ecosystems — FastAPI, pydantic, httpx, keyring, and
93
- ruamel.yaml on the backend; React and Vite on the front.
94
-
95
- Testing uses **[VidaiMock](https://github.com/vidaiUK/VidaiMock)**, an
96
- Apache-2.0 mock LLM server, as the offline agent endpoint in CI.
97
-
98
- DuckDB is a trademark of the DuckDB Foundation. DataCharter is an independent
99
- project and is not affiliated with or endorsed by the DuckDB Foundation.
100
-
101
- ## License
102
-
103
- [Apache-2.0](LICENSE)
File without changes
File without changes
File without changes
File without changes