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.
- {datacharter-0.3.0 → datacharter-0.3.2}/CHANGELOG.md +17 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/PKG-INFO +51 -39
- datacharter-0.3.2/README.md +115 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/__init__.py +1 -1
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/contracts/loader.py +4 -2
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/app.py +9 -26
- datacharter-0.3.2/src/datacharter/server/security.py +56 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/contracts/test_loader.py +18 -0
- datacharter-0.3.2/tests/server/test_security.py +52 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_cli_init.py +23 -0
- datacharter-0.3.0/README.md +0 -103
- {datacharter-0.3.0 → datacharter-0.3.2}/.github/workflows/banned-terms.yml +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/.github/workflows/release.yml +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/.github/workflows/test.yml +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/.gitignore +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/CODE_OF_CONDUCT.md +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/CONTRIBUTING.md +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/LICENSE +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/SECURITY.md +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/THIRD-PARTY-NOTICES.md +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/TRADEMARKS.md +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/brand/README.md +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/brand/demo.gif +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/brand/hero.png +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/brand/mark.svg +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/brand/screenshot-m4-agent.png +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/brand/screenshot-m5-complete.png +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/brand/screenshot-m5-core.png +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/brand/screenshot-m5-features.png +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/compose.yaml +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/docs/_config.yml +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/docs/about.md +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/docs/agent.md +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/docs/assets/add-source.gif +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/docs/assets/drag-drop.gif +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/docs/assets/hero.png +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/docs/assets/screenshot.png +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/docs/assets/snapshot.gif +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/docs/charter-yaml.md +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/docs/cli.md +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/docs/faq.md +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/docs/index.html +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/docs/mcp.md +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/docs/quickstart.md +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/docs/security.md +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/docs/sources.md +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/docs/workspace.md +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/pyproject.toml +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/scripts/build_ui.sh +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/agent/__init__.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/agent/cache.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/agent/llm.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/agent/loop.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/agent/tools.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/cli.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/contracts/__init__.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/contracts/metrics.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/contracts/pii.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/contracts/resolve.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/contracts/secrets.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/contracts/writer.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/__init__.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/aggregate.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/guard.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/provenance.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/pushdown.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/scrub.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/session.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/snowflake.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/sources.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/engine/statekey.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/mcp/__init__.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/mcp/server.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/models.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/__init__.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/llm_admin.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/source_admin.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/abap-DLDM7-KI.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/apex-DNDY2TF8.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/azcli-Y6nb8tq_.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/bat-BwHxbl9M.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/bicep-CFznDFnq.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/cameligo-Bf6VGUru.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/clojure-Dnu-v4kV.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/codicon-Brq4_Ui5.ttf +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/coffee-Bd8akH9Z.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/cpp-BbWJElDN.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/csharp-Co3qMtFm.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/csp-D-4FJmMZ.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/css-DdJfP1eB.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/css.worker-CqwWTjeG.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/cssMode-BuAVOLr4.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/cypher-cTPe9QuQ.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/dart-BOtBlQCF.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/dockerfile-BG73LgW2.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/ecl-BEgZUVRK.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/editor.worker-DCKwvLbM.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/elixir-BkW5O-1t.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/flow9-BeJ5waoc.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/freemarker2-DM0thzwF.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/fsharp-PahG7c26.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/go-acbASCJo.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/graphql-BxJiqAUM.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/handlebars-BkMf3Qj4.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/hcl-DtV1sZF8.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/html-KeNy2AFd.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/html.worker-DhJ4VZMp.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/htmlMode-CN5rSB9p.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/index-CX4OWOHb.css +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/index-CuaSFI7T.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/ini-Kd9XrMLS.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/java-CXBNlu9o.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/javascript-CZu1iw4q.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/json.worker-BF9eV5mS.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/jsonMode-BtMXpE2Q.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/julia-cl7-CwDS.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/kotlin-s7OhZKlX.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/less-9HpZscsL.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/lexon-OrD6JF1K.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/liquid-mhqehfbE.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/lspLanguageFeatures-D1NXFkgg.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/lua-Cyyb5UIc.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/m3-B8OfTtLu.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/markdown-BFxVWTOG.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/mdx-69yOd9f1.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/mips-CiqrrVzr.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/monaco-CB2SckMT.css +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/monaco-DNze5gXB.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/msdax-DmeGPVcC.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/mysql-C_tMU-Nz.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/objective-c-BDtDVThU.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/pascal-vHIfCaH5.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/pascaligo-DtZ0uQbO.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/perl-Ub6l9XKa.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/pgsql-BlNEE0v7.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/php-BBUBE1dy.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/pla-DSh2-awV.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/postiats-CocnycG-.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/powerquery-tScXyioY.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/powershell-COWaemsV.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/protobuf-Brw8urJB.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/pug-8SOpv6rk.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/python-C5qpjWC4.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/qsharp-Bw9ernYp.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/r-j7ic8hl3.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/razor-fg0l3XC9.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/redis-Bu5POkcn.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/redshift-Bs9aos_-.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/restructuredtext-CqXO7rUv.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/ruby-zBfavPgS.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/rust-BzKRNQWT.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/sb-BBc9UKZt.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/scala-D9hQfWCl.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/scheme-BPhDTwHR.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/scss-CBJaRo0y.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/shell-DiJ1NA_G.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/solidity-Db0IVjzk.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/sophia-CnS9iZB_.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/sparql-CJmd_6j2.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/sql-ClhHkBeG.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/st-CHwy0fLd.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/swift-Bqt4WxQ4.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/systemverilog-Bs9z6M-B.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/tcl-Dm6ycUr_.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/ts.worker-DTZAwq0V.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/tsMode-CaXNdXrQ.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/twig-Csy3S7wG.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/typescript-CCbVPqil.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/typespec-Btyra-wh.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/vb-Db0cS2oM.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/wgsl-BTesnYfV.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/workers-CwEVRSMl.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/xml-CIpcyvha.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/assets/yaml-CiikAWeE.js +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/src/datacharter/server/static/index.html +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/agent/test_agent.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/agent/test_cache.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/agent/test_llm_wire.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/contracts/test_metrics.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/contracts/test_pii.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/contracts/test_secrets_store.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/contracts/test_writer.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_aggregate.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_diff.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_federation.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_guard.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_provenance.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_pushdown.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_scrub.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_session.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_source_lifecycle.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/engine/test_sources.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/integration/seed/mysql.sql +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/integration/seed/postgres.sql +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/integration/test_live_sources.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/mcp/test_mcp_server.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/server/test_api.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/server/test_llm_admin.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/server/test_offline.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/server/test_source_admin.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/server/test_sources_api.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_agent_vidaimock_e2e.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_drift.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_explain.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_packaging_e2e.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_sample.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_scaffold.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_secrets_cli.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/tests/test_snapshot_recheck.py +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/index.html +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/package-lock.json +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/package.json +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/App.tsx +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/api.ts +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/ChartPanel.tsx +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/ChatPanel.tsx +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/HelpModal.tsx +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/LLMConfig.tsx +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/QueryFiles.tsx +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/ResultsGrid.tsx +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/SourceForm.tsx +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/SourceTree.tsx +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/SourcesView.tsx +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/components/Tutorial.tsx +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/lib/caption.ts +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/lib/chartSpec.ts +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/lib/columns.ts +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/lib/useResize.ts +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/main.tsx +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/monaco.ts +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/styles.css +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/src/vite-env.d.ts +0 -0
- {datacharter-0.3.0 → datacharter-0.3.2}/ui/tsconfig.json +0 -0
- {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.
|
|
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
|

|
|
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
|
|
70
|
-
uvx datacharter serve
|
|
71
|
-
# →
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
export OPENAI_BASE_URL=...
|
|
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
|
-
|
|
107
|
+
…or run **fully local** — no API key, no data leaves your machine (requires
|
|
108
|
+
[Ollama](https://ollama.com)):
|
|
89
109
|
|
|
90
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
`
|
|
101
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
+
[](https://pypi.org/project/datacharter/)
|
|
6
|
+
[](https://pypi.org/project/datacharter/)
|
|
7
|
+
[](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
|
+

|
|
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)
|
|
@@ -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
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
datacharter-0.3.0/README.md
DELETED
|
@@ -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
|
-
[](https://pypi.org/project/datacharter/)
|
|
6
|
-
[](https://pypi.org/project/datacharter/)
|
|
7
|
-
[](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
|
-

|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|