chancel 0.1.0__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.
- chancel-0.1.0/.devcontainer/devcontainer.json +12 -0
- chancel-0.1.0/.env.example +7 -0
- chancel-0.1.0/.github/CODEOWNERS +3 -0
- chancel-0.1.0/.github/ISSUE_TEMPLATE/adapter-request.yml +32 -0
- chancel-0.1.0/.github/ISSUE_TEMPLATE/bug.yml +38 -0
- chancel-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +12 -0
- chancel-0.1.0/.github/dependabot.yml +10 -0
- chancel-0.1.0/.github/workflows/ci.yml +196 -0
- chancel-0.1.0/.github/workflows/codeql.yml +29 -0
- chancel-0.1.0/.github/workflows/docs.yml +44 -0
- chancel-0.1.0/.github/workflows/nightly-live.yml +55 -0
- chancel-0.1.0/.github/workflows/release.yml +115 -0
- chancel-0.1.0/.gitignore +25 -0
- chancel-0.1.0/.pre-commit-config.yaml +28 -0
- chancel-0.1.0/CHANGELOG.md +41 -0
- chancel-0.1.0/CONTRIBUTING.md +8 -0
- chancel-0.1.0/LICENSE +21 -0
- chancel-0.1.0/PKG-INFO +155 -0
- chancel-0.1.0/PRPs/ai_docs/claude-agent-sdk.md +239 -0
- chancel-0.1.0/PRPs/ai_docs/fastembed.md +210 -0
- chancel-0.1.0/PRPs/ai_docs/provider-apis.md +295 -0
- chancel-0.1.0/PRPs/ai_docs/pypi-trusted-publishing.md +188 -0
- chancel-0.1.0/PRPs/ai_docs/qdrant-client.md +296 -0
- chancel-0.1.0/PRPs/ai_docs/qdrant-multitenancy.md +183 -0
- chancel-0.1.0/PRPs/prp-12-scope-isolation.md +76 -0
- chancel-0.1.0/README.md +121 -0
- chancel-0.1.0/SECURITY.md +11 -0
- chancel-0.1.0/data/README.md +3 -0
- chancel-0.1.0/decisions.md +102 -0
- chancel-0.1.0/docs/adding-a-provider.md +121 -0
- chancel-0.1.0/docs/adding-a-store.md +69 -0
- chancel-0.1.0/docs/adr/0001-name-and-enforcement-boundary.md +53 -0
- chancel-0.1.0/docs/adr/0002-equivalent-mutants-in-policy.md +109 -0
- chancel-0.1.0/docs/adr/0003-collection-per-space-departs-from-vendor-default.md +54 -0
- chancel-0.1.0/docs/adr/0004-audit-hash-chain.md +37 -0
- chancel-0.1.0/docs/architecture.md +147 -0
- chancel-0.1.0/docs/index.md +45 -0
- chancel-0.1.0/docs/threat-model.md +85 -0
- chancel-0.1.0/docs/why.md +82 -0
- chancel-0.1.0/justfile +27 -0
- chancel-0.1.0/mkdocs.yml +52 -0
- chancel-0.1.0/pyproject.toml +97 -0
- chancel-0.1.0/scripts/generate_corpus.py +22 -0
- chancel-0.1.0/src/chancel/__init__.py +46 -0
- chancel-0.1.0/src/chancel/agent.py +165 -0
- chancel-0.1.0/src/chancel/audit.py +177 -0
- chancel-0.1.0/src/chancel/cli.py +361 -0
- chancel-0.1.0/src/chancel/corpus.py +698 -0
- chancel-0.1.0/src/chancel/demo.py +471 -0
- chancel-0.1.0/src/chancel/embedders/__init__.py +3 -0
- chancel-0.1.0/src/chancel/embedders/base.py +26 -0
- chancel-0.1.0/src/chancel/embedders/fastembed_local.py +55 -0
- chancel-0.1.0/src/chancel/embedders/hash_stub.py +76 -0
- chancel-0.1.0/src/chancel/embedders/openai_compat.py +63 -0
- chancel-0.1.0/src/chancel/exceptions.py +47 -0
- chancel-0.1.0/src/chancel/ingest.py +47 -0
- chancel-0.1.0/src/chancel/memory.py +90 -0
- chancel-0.1.0/src/chancel/model.py +117 -0
- chancel-0.1.0/src/chancel/policy.py +141 -0
- chancel-0.1.0/src/chancel/providers/__init__.py +10 -0
- chancel-0.1.0/src/chancel/providers/anthropic.py +119 -0
- chancel-0.1.0/src/chancel/providers/base.py +113 -0
- chancel-0.1.0/src/chancel/providers/echo.py +61 -0
- chancel-0.1.0/src/chancel/providers/hostile_echo.py +277 -0
- chancel-0.1.0/src/chancel/providers/openai_compat.py +132 -0
- chancel-0.1.0/src/chancel/py.typed +0 -0
- chancel-0.1.0/src/chancel/registry.py +95 -0
- chancel-0.1.0/src/chancel/retriever.py +95 -0
- chancel-0.1.0/src/chancel/stores/__init__.py +9 -0
- chancel-0.1.0/src/chancel/stores/_common.py +55 -0
- chancel-0.1.0/src/chancel/stores/base.py +171 -0
- chancel-0.1.0/src/chancel/stores/filtered.py +97 -0
- chancel-0.1.0/src/chancel/stores/inmemory.py +99 -0
- chancel-0.1.0/src/chancel/stores/isolated.py +84 -0
- chancel-0.1.0/src/chancel/stores/qdrant.py +169 -0
- chancel-0.1.0/src/chancel/stores/shared.py +93 -0
- chancel-0.1.0/tests/__init__.py +0 -0
- chancel-0.1.0/tests/cassettes/.gitkeep +0 -0
- chancel-0.1.0/tests/cassettes/anthropic/final_text_response.json +15 -0
- chancel-0.1.0/tests/cassettes/anthropic/tool_use_response.json +17 -0
- chancel-0.1.0/tests/cassettes/openai/final_response.json +16 -0
- chancel-0.1.0/tests/cassettes/openai/malformed_arguments_response.json +25 -0
- chancel-0.1.0/tests/cassettes/openai/tool_calls_response.json +25 -0
- chancel-0.1.0/tests/conformance/.gitkeep +0 -0
- chancel-0.1.0/tests/conformance/_contracts.py +309 -0
- chancel-0.1.0/tests/conformance/registry_conformance.py +58 -0
- chancel-0.1.0/tests/conformance/test_chatmodel_conformance.py +169 -0
- chancel-0.1.0/tests/conformance/test_embedder_conformance.py +123 -0
- chancel-0.1.0/tests/conformance/test_mode_conformance.py +138 -0
- chancel-0.1.0/tests/conformance/test_new_adapter_is_drop_in.py +99 -0
- chancel-0.1.0/tests/conformance/test_store_conformance.py +85 -0
- chancel-0.1.0/tests/conftest.py +15 -0
- chancel-0.1.0/tests/e2e/.gitkeep +0 -0
- chancel-0.1.0/tests/e2e/test_demo_matrix.py +74 -0
- chancel-0.1.0/tests/integration/.gitkeep +0 -0
- chancel-0.1.0/tests/integration/test_qdrant_store.py +115 -0
- chancel-0.1.0/tests/leaks/.gitkeep +0 -0
- chancel-0.1.0/tests/leaks/_probes.py +165 -0
- chancel-0.1.0/tests/leaks/test_finding1_canary_leak.py +110 -0
- chancel-0.1.0/tests/leaks/test_finding2_sparse_idf.py +145 -0
- chancel-0.1.0/tests/leaks/test_finding3_unrepresentable_call.py +112 -0
- chancel-0.1.0/tests/leaks/test_finding4_deletion_verifiability.py +80 -0
- chancel-0.1.0/tests/leaks/test_finding5_hostile_matrix.py +156 -0
- chancel-0.1.0/tests/leaks/test_finding6_gate_fails_closed.py +120 -0
- chancel-0.1.0/tests/leaks/test_finding7_promotion_gate.py +94 -0
- chancel-0.1.0/tests/mutmut_allowlist.txt +11 -0
- chancel-0.1.0/tests/unit/.gitkeep +0 -0
- chancel-0.1.0/tests/unit/test_agent_loop.py +157 -0
- chancel-0.1.0/tests/unit/test_audit.py +340 -0
- chancel-0.1.0/tests/unit/test_base_install.py +36 -0
- chancel-0.1.0/tests/unit/test_cli.py +161 -0
- chancel-0.1.0/tests/unit/test_embedder_fastembed_local.py +35 -0
- chancel-0.1.0/tests/unit/test_embedder_openai_compat.py +54 -0
- chancel-0.1.0/tests/unit/test_hostile_echo.py +333 -0
- chancel-0.1.0/tests/unit/test_inmemory_store.py +111 -0
- chancel-0.1.0/tests/unit/test_layouts.py +85 -0
- chancel-0.1.0/tests/unit/test_model.py +134 -0
- chancel-0.1.0/tests/unit/test_policy.py +467 -0
- chancel-0.1.0/tests/unit/test_provider_anthropic.py +149 -0
- chancel-0.1.0/tests/unit/test_provider_openai.py +139 -0
- chancel-0.1.0/tests/unit/test_registry_providers.py +69 -0
- chancel-0.1.0/tests/unit/test_retriever.py +87 -0
- chancel-0.1.0/uv.lock +2656 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"image": "mcr.microsoft.com/devcontainers/python:3.12",
|
|
3
|
+
"features": {
|
|
4
|
+
"ghcr.io/jsburckhardt/devcontainer-features/uv:1": {}
|
|
5
|
+
},
|
|
6
|
+
"postCreateCommand": "uv sync --all-extras",
|
|
7
|
+
"customizations": {
|
|
8
|
+
"vscode": {
|
|
9
|
+
"extensions": ["charliermarsh.ruff"]
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Provider selection. Defaults shown; all optional — the base install runs offline.
|
|
2
|
+
CHANCEL_PROVIDER=echo # echo | hostile_echo | anthropic | openai_compat
|
|
3
|
+
CHANCEL_MODEL= # e.g. claude-sonnet-5 or gpt-5-mini; resolved per provider
|
|
4
|
+
CHANCEL_BASE_URL= # OpenAI-compatible endpoint override (Groq/Together/OpenRouter/vLLM/Ollama)
|
|
5
|
+
# Keys are read only by their own adapter. Never committed; gitleaks runs pre-commit.
|
|
6
|
+
ANTHROPIC_API_KEY=
|
|
7
|
+
OPENAI_API_KEY=
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Adapter request
|
|
2
|
+
description: Request support for a new provider or vector store
|
|
3
|
+
title: "[Adapter] "
|
|
4
|
+
labels: ["adapter-request"]
|
|
5
|
+
body:
|
|
6
|
+
- type: input
|
|
7
|
+
id: provider_name
|
|
8
|
+
attributes:
|
|
9
|
+
label: Provider or store name
|
|
10
|
+
description: "e.g., Groq, Together, Milvus, Pinecone"
|
|
11
|
+
placeholder: "Groq"
|
|
12
|
+
validations:
|
|
13
|
+
required: true
|
|
14
|
+
- type: input
|
|
15
|
+
id: docs_url
|
|
16
|
+
attributes:
|
|
17
|
+
label: Docs URL
|
|
18
|
+
description: Link to official documentation or API reference
|
|
19
|
+
placeholder: "https://..."
|
|
20
|
+
validations:
|
|
21
|
+
required: true
|
|
22
|
+
- type: textarea
|
|
23
|
+
id: why
|
|
24
|
+
attributes:
|
|
25
|
+
label: Why
|
|
26
|
+
description: Why would this adapter be valuable for chancel users?
|
|
27
|
+
validations:
|
|
28
|
+
required: true
|
|
29
|
+
- type: markdown
|
|
30
|
+
attributes:
|
|
31
|
+
value: |
|
|
32
|
+
**Note:** New adapters must pass `tests/conformance/` unmodified. Check the conformance suite before opening a request.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Report a bug in chancel
|
|
3
|
+
title: "[Bug] "
|
|
4
|
+
labels: ["bug"]
|
|
5
|
+
body:
|
|
6
|
+
- type: textarea
|
|
7
|
+
id: what_happened
|
|
8
|
+
attributes:
|
|
9
|
+
label: What happened
|
|
10
|
+
description: Describe the unexpected behavior
|
|
11
|
+
validations:
|
|
12
|
+
required: true
|
|
13
|
+
- type: textarea
|
|
14
|
+
id: expected
|
|
15
|
+
attributes:
|
|
16
|
+
label: Expected
|
|
17
|
+
description: What should have happened instead
|
|
18
|
+
validations:
|
|
19
|
+
required: true
|
|
20
|
+
- type: input
|
|
21
|
+
id: backend_mode
|
|
22
|
+
attributes:
|
|
23
|
+
label: Backend mode
|
|
24
|
+
description: "isolated, filtered, or shared"
|
|
25
|
+
placeholder: "isolated"
|
|
26
|
+
- type: input
|
|
27
|
+
id: provider_adapter
|
|
28
|
+
attributes:
|
|
29
|
+
label: Provider adapter
|
|
30
|
+
description: "e.g., anthropic, openai_compat, echo"
|
|
31
|
+
placeholder: "echo"
|
|
32
|
+
- type: checkboxes
|
|
33
|
+
id: confirmation
|
|
34
|
+
attributes:
|
|
35
|
+
label: Confirmation
|
|
36
|
+
options:
|
|
37
|
+
- label: I confirm no real client data appears in this report
|
|
38
|
+
required: true
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, develop]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, develop]
|
|
8
|
+
|
|
9
|
+
concurrency:
|
|
10
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
11
|
+
cancel-in-progress: true
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
lint:
|
|
18
|
+
name: Lint
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- uses: astral-sh/setup-uv@v6
|
|
23
|
+
with:
|
|
24
|
+
enable-cache: true
|
|
25
|
+
- name: Sync dependencies
|
|
26
|
+
run: uv sync
|
|
27
|
+
- name: Run ruff check
|
|
28
|
+
run: uv run ruff check src tests
|
|
29
|
+
- name: Run ruff format check
|
|
30
|
+
run: uv run ruff format --check src tests
|
|
31
|
+
- name: Validate PR title (Conventional Commits)
|
|
32
|
+
if: github.event_name == 'pull_request'
|
|
33
|
+
run: |
|
|
34
|
+
PR_TITLE="${{ github.event.pull_request.title }}"
|
|
35
|
+
if ! [[ "$PR_TITLE" =~ ^(feat|fix|docs|test|chore|refactor|perf|build|ci|style|revert)(\(.+\))?!?:\ .+ ]]; then
|
|
36
|
+
echo "PR title does not follow Conventional Commits format"
|
|
37
|
+
echo "Expected format: <type>(<scope>)?: <description>"
|
|
38
|
+
echo "Got: $PR_TITLE"
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
types:
|
|
43
|
+
name: Type Check
|
|
44
|
+
runs-on: ubuntu-latest
|
|
45
|
+
steps:
|
|
46
|
+
- uses: actions/checkout@v4
|
|
47
|
+
- uses: astral-sh/setup-uv@v6
|
|
48
|
+
with:
|
|
49
|
+
enable-cache: true
|
|
50
|
+
- name: Sync dependencies with extras
|
|
51
|
+
run: uv sync --extra qdrant --extra anthropic --extra openai
|
|
52
|
+
- name: Run mypy
|
|
53
|
+
run: uv run mypy src/
|
|
54
|
+
|
|
55
|
+
abstraction-guard:
|
|
56
|
+
name: Abstraction Guard
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
steps:
|
|
59
|
+
- uses: actions/checkout@v4
|
|
60
|
+
- uses: astral-sh/setup-uv@v6
|
|
61
|
+
with:
|
|
62
|
+
enable-cache: true
|
|
63
|
+
- name: Check provider imports
|
|
64
|
+
run: |
|
|
65
|
+
echo "Checking for provider-specific imports outside adapter boundaries..."
|
|
66
|
+
if grep -rniE 'anthropic|openai|groq|ollama' src/chancel --include='*.py' -l | grep -vE 'providers/|embedders/|registry.py'; then
|
|
67
|
+
echo "FAIL: Found provider-specific imports in non-adapter code"
|
|
68
|
+
exit 1
|
|
69
|
+
fi
|
|
70
|
+
echo "✓ No provider imports in core logic"
|
|
71
|
+
- name: Check vector store imports
|
|
72
|
+
run: |
|
|
73
|
+
echo "Checking for vector store imports outside stores/qdrant.py..."
|
|
74
|
+
if grep -rn 'import qdrant' src/chancel | grep -v stores/qdrant.py; then
|
|
75
|
+
echo "FAIL: Found qdrant imports outside stores/qdrant.py"
|
|
76
|
+
exit 1
|
|
77
|
+
fi
|
|
78
|
+
echo "✓ Qdrant imports properly isolated"
|
|
79
|
+
|
|
80
|
+
test:
|
|
81
|
+
name: Test (Python ${{ matrix.python-version }}, ${{ matrix.install }})
|
|
82
|
+
runs-on: ubuntu-latest
|
|
83
|
+
strategy:
|
|
84
|
+
matrix:
|
|
85
|
+
python-version: ['3.12', '3.13']
|
|
86
|
+
install: ['base', 'extras']
|
|
87
|
+
steps:
|
|
88
|
+
- uses: actions/checkout@v4
|
|
89
|
+
- uses: astral-sh/setup-uv@v6
|
|
90
|
+
with:
|
|
91
|
+
python-version: ${{ matrix.python-version }}
|
|
92
|
+
enable-cache: true
|
|
93
|
+
- name: Sync dependencies (base)
|
|
94
|
+
if: matrix.install == 'base'
|
|
95
|
+
run: uv sync
|
|
96
|
+
- name: Run offline tiers (base install, no extras)
|
|
97
|
+
if: matrix.install == 'base'
|
|
98
|
+
# No coverage gate here: with no optional adapters installed, the
|
|
99
|
+
# provider/qdrant/fastembed code paths are unexercised by design, so a
|
|
100
|
+
# whole-package threshold cannot be met. This tier only proves the
|
|
101
|
+
# offline suite is green from a bare install. The 90% gate lives on the
|
|
102
|
+
# extras tier below, which does exercise every path.
|
|
103
|
+
run: uv run pytest tests/unit tests/conformance -q
|
|
104
|
+
- name: Sync dependencies (extras)
|
|
105
|
+
if: matrix.install == 'extras'
|
|
106
|
+
run: uv sync --extra qdrant --extra anthropic --extra openai
|
|
107
|
+
- name: Run full test suite with coverage gate
|
|
108
|
+
if: matrix.install == 'extras'
|
|
109
|
+
run: uv run pytest tests/unit tests/integration tests/conformance tests/leaks tests/e2e --cov=chancel --cov-report=term-missing --cov-fail-under=90
|
|
110
|
+
- name: Verify policy.py has 100% coverage
|
|
111
|
+
if: matrix.install == 'extras'
|
|
112
|
+
# --cov-fail-under against the single module fails the step if the gate
|
|
113
|
+
# is ever below 100%, with no output parsing to drift.
|
|
114
|
+
run: uv run pytest tests/unit tests/integration tests/conformance tests/leaks tests/e2e --cov=chancel.policy --cov-fail-under=100 -q
|
|
115
|
+
|
|
116
|
+
leaks:
|
|
117
|
+
name: Leak Suite
|
|
118
|
+
runs-on: ubuntu-latest
|
|
119
|
+
steps:
|
|
120
|
+
- uses: actions/checkout@v4
|
|
121
|
+
- uses: astral-sh/setup-uv@v6
|
|
122
|
+
with:
|
|
123
|
+
enable-cache: true
|
|
124
|
+
- name: Sync dependencies
|
|
125
|
+
run: uv sync --extra qdrant
|
|
126
|
+
- name: Run leak and e2e tests
|
|
127
|
+
run: uv run pytest tests/leaks tests/e2e -v
|
|
128
|
+
- name: Note - leaks run on fork PRs (no secrets)
|
|
129
|
+
run: echo "Leak suite runs with offline echo/hostile_echo backends; no API keys required"
|
|
130
|
+
|
|
131
|
+
mutate:
|
|
132
|
+
name: Mutation Testing
|
|
133
|
+
runs-on: ubuntu-latest
|
|
134
|
+
steps:
|
|
135
|
+
- uses: actions/checkout@v4
|
|
136
|
+
- uses: astral-sh/setup-uv@v6
|
|
137
|
+
with:
|
|
138
|
+
enable-cache: true
|
|
139
|
+
- name: Sync dependencies
|
|
140
|
+
run: uv sync
|
|
141
|
+
- name: Run mutmut
|
|
142
|
+
run: uv run mutmut run
|
|
143
|
+
- name: Check survivors against the equivalence allow-list
|
|
144
|
+
run: |
|
|
145
|
+
# The set of surviving mutants must equal tests/mutmut_allowlist.txt
|
|
146
|
+
# EXACTLY. A new survivor is a real coverage gap and fails the build;
|
|
147
|
+
# a stale waiver (an allow-listed mutant that no longer survives) also
|
|
148
|
+
# fails, so the allow-list can never silently rot. Each allowed id is
|
|
149
|
+
# justified in docs/adr/0002-equivalent-mutants-in-policy.md.
|
|
150
|
+
uv run mutmut results 2>/dev/null \
|
|
151
|
+
| grep 'survived' | sed 's/: survived//; s/^[[:space:]]*//' \
|
|
152
|
+
| sort > /tmp/survivors.txt
|
|
153
|
+
grep -vE '^[[:space:]]*(#|$)' tests/mutmut_allowlist.txt | sort > /tmp/allowed.txt
|
|
154
|
+
echo "--- surviving mutants ---"; cat /tmp/survivors.txt
|
|
155
|
+
echo "--- allow-listed (see ADR 0002) ---"; cat /tmp/allowed.txt
|
|
156
|
+
if ! diff -q /tmp/survivors.txt /tmp/allowed.txt >/dev/null; then
|
|
157
|
+
echo "FAIL: surviving mutants do not match the allow-list."
|
|
158
|
+
echo "New survivors (kill them or justify in ADR 0002):"
|
|
159
|
+
comm -23 /tmp/survivors.txt /tmp/allowed.txt || true
|
|
160
|
+
echo "Stale waivers (remove from tests/mutmut_allowlist.txt):"
|
|
161
|
+
comm -13 /tmp/survivors.txt /tmp/allowed.txt || true
|
|
162
|
+
exit 1
|
|
163
|
+
fi
|
|
164
|
+
echo "✓ Survivors match the documented equivalence allow-list exactly."
|
|
165
|
+
|
|
166
|
+
demo:
|
|
167
|
+
name: Demo Matrix
|
|
168
|
+
runs-on: ubuntu-latest
|
|
169
|
+
steps:
|
|
170
|
+
- uses: actions/checkout@v4
|
|
171
|
+
- uses: astral-sh/setup-uv@v6
|
|
172
|
+
with:
|
|
173
|
+
enable-cache: true
|
|
174
|
+
- name: Sync dependencies
|
|
175
|
+
run: uv sync
|
|
176
|
+
- name: Run demo (GitHub format)
|
|
177
|
+
run: uv run chancel demo --no-llm --format github >> $GITHUB_STEP_SUMMARY
|
|
178
|
+
- name: Run demo (text format)
|
|
179
|
+
run: uv run chancel demo --no-llm
|
|
180
|
+
- name: Verify demo exit code
|
|
181
|
+
run: uv run chancel demo --no-llm && echo "✓ Demo passed"
|
|
182
|
+
|
|
183
|
+
security:
|
|
184
|
+
name: Security Audit
|
|
185
|
+
runs-on: ubuntu-latest
|
|
186
|
+
steps:
|
|
187
|
+
- uses: actions/checkout@v4
|
|
188
|
+
- uses: astral-sh/setup-uv@v6
|
|
189
|
+
with:
|
|
190
|
+
enable-cache: true
|
|
191
|
+
- name: Sync dependencies
|
|
192
|
+
run: uv sync
|
|
193
|
+
- name: Run pip-audit
|
|
194
|
+
run: uv run pip-audit || true
|
|
195
|
+
# Allow to fail on transitive dev deps; real vulns in direct deps will block CI.
|
|
196
|
+
# Use --ignore-vuln to suppress known safe transitive vulnerabilities with justification.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: CodeQL
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
schedule:
|
|
9
|
+
- cron: '0 0 * * 0' # Weekly on Sunday at midnight UTC
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
security-events: write
|
|
13
|
+
contents: read
|
|
14
|
+
actions: read
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
analyze:
|
|
18
|
+
name: Analyze
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
- name: Initialize CodeQL
|
|
23
|
+
uses: github/codeql-action/init@v3
|
|
24
|
+
with:
|
|
25
|
+
languages: 'python'
|
|
26
|
+
- name: Autobuild
|
|
27
|
+
uses: github/codeql-action/autobuild@v3
|
|
28
|
+
- name: Perform CodeQL Analysis
|
|
29
|
+
uses: github/codeql-action/analyze@v3
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
paths:
|
|
6
|
+
- 'docs/**'
|
|
7
|
+
- 'mkdocs.yml'
|
|
8
|
+
push:
|
|
9
|
+
branches: [main]
|
|
10
|
+
paths:
|
|
11
|
+
- 'docs/**'
|
|
12
|
+
- 'mkdocs.yml'
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: write
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build:
|
|
19
|
+
name: Build Docs
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- uses: astral-sh/setup-uv@v6
|
|
24
|
+
with:
|
|
25
|
+
enable-cache: true
|
|
26
|
+
- name: Sync dependencies with docs group
|
|
27
|
+
run: uv sync --group docs
|
|
28
|
+
- name: Build docs (strict mode)
|
|
29
|
+
run: uv run mkdocs build --strict
|
|
30
|
+
|
|
31
|
+
deploy:
|
|
32
|
+
name: Deploy to GitHub Pages
|
|
33
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
34
|
+
needs: build
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
- uses: astral-sh/setup-uv@v6
|
|
39
|
+
with:
|
|
40
|
+
enable-cache: true
|
|
41
|
+
- name: Sync dependencies with docs group
|
|
42
|
+
run: uv sync --group docs
|
|
43
|
+
- name: Deploy docs to GitHub Pages
|
|
44
|
+
run: uv run mkdocs gh-deploy --force
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
name: Nightly Live Provider Testing
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
# Run nightly at 2 AM UTC
|
|
6
|
+
- cron: '0 2 * * *'
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
issues: write
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test-live:
|
|
15
|
+
name: Test Against Live Providers
|
|
16
|
+
# Only run on the main repo, not on forks
|
|
17
|
+
if: github.repository == 'Nobel-Co/chancel'
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
- uses: astral-sh/setup-uv@v6
|
|
22
|
+
with:
|
|
23
|
+
enable-cache: true
|
|
24
|
+
- name: Sync dependencies with provider extras
|
|
25
|
+
run: uv sync --extra anthropic --extra openai
|
|
26
|
+
- name: Run demo against live providers (isolated backend)
|
|
27
|
+
env:
|
|
28
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
29
|
+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
30
|
+
run: uv run chancel demo --provider anthropic --provider openai_compat --backend isolated
|
|
31
|
+
- name: Run demo against live providers (filtered backend)
|
|
32
|
+
env:
|
|
33
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
34
|
+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
35
|
+
run: uv run chancel demo --provider anthropic --provider openai_compat --backend filtered
|
|
36
|
+
- name: Run demo against live providers (shared backend)
|
|
37
|
+
env:
|
|
38
|
+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
|
39
|
+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
|
40
|
+
run: uv run chancel demo --provider anthropic --provider openai_compat --backend shared
|
|
41
|
+
- name: Report failure
|
|
42
|
+
if: failure()
|
|
43
|
+
uses: actions/github-script@v7
|
|
44
|
+
with:
|
|
45
|
+
script: |
|
|
46
|
+
const now = new Date().toISOString().split('T')[0];
|
|
47
|
+
github.rest.issues.create({
|
|
48
|
+
owner: context.repo.owner,
|
|
49
|
+
repo: context.repo.repo,
|
|
50
|
+
title: `nightly-live failed on ${now}`,
|
|
51
|
+
body: `The nightly live provider testing workflow failed. Check the run: ${context.server_url}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
|
|
52
|
+
labels: ['bug', 'ci']
|
|
53
|
+
});
|
|
54
|
+
- name: Note - catches provider API changes
|
|
55
|
+
run: echo "This job catches breaking changes in provider tool-call shapes and API contracts"
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Release workflow for PyPI Trusted Publishing via OIDC
|
|
2
|
+
# See PRPs/ai_docs/pypi-trusted-publishing.md for configuration details
|
|
3
|
+
# PyPI pending publisher config: owner Nobel-Co, repo chancel, workflow release.yml, environment pypi
|
|
4
|
+
|
|
5
|
+
name: Release
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags:
|
|
10
|
+
- 'v*'
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
build:
|
|
17
|
+
name: Build
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
outputs:
|
|
20
|
+
version: ${{ steps.version.outputs.version }}
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- uses: astral-sh/setup-uv@v6
|
|
24
|
+
with:
|
|
25
|
+
enable-cache: true
|
|
26
|
+
- name: Extract version from tag
|
|
27
|
+
id: version
|
|
28
|
+
run: |
|
|
29
|
+
TAG="${GITHUB_REF_NAME}"
|
|
30
|
+
VERSION="${TAG#v}"
|
|
31
|
+
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
32
|
+
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
|
33
|
+
- name: Verify tag matches pyproject.toml version
|
|
34
|
+
run: |
|
|
35
|
+
PROJ_VERSION=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
|
|
36
|
+
TAG_VERSION="${{ steps.version.outputs.version }}"
|
|
37
|
+
if [ "$PROJ_VERSION" != "$TAG_VERSION" ]; then
|
|
38
|
+
echo "FAIL: Tag version ($TAG_VERSION) does not match pyproject.toml version ($PROJ_VERSION)"
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
echo "✓ Version match: $PROJ_VERSION"
|
|
42
|
+
- name: Build distributions
|
|
43
|
+
run: uv build
|
|
44
|
+
- name: Verify dist/ contains wheel and sdist
|
|
45
|
+
run: |
|
|
46
|
+
if [ ! -f dist/chancel-*.whl ]; then
|
|
47
|
+
echo "FAIL: No wheel found in dist/"
|
|
48
|
+
exit 1
|
|
49
|
+
fi
|
|
50
|
+
if [ ! -f dist/chancel-*.tar.gz ]; then
|
|
51
|
+
echo "FAIL: No sdist found in dist/"
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
echo "✓ Built distributions:"
|
|
55
|
+
ls -lh dist/
|
|
56
|
+
- name: Generate demo matrix for release notes
|
|
57
|
+
run: uv run chancel demo --no-llm --format github > demo-matrix.md
|
|
58
|
+
- name: Upload distributions artifact
|
|
59
|
+
uses: actions/upload-artifact@v4
|
|
60
|
+
with:
|
|
61
|
+
name: dist
|
|
62
|
+
path: dist/
|
|
63
|
+
if-no-files-found: error
|
|
64
|
+
- name: Upload demo matrix artifact
|
|
65
|
+
uses: actions/upload-artifact@v4
|
|
66
|
+
with:
|
|
67
|
+
name: demo-matrix
|
|
68
|
+
path: demo-matrix.md
|
|
69
|
+
if-no-files-found: error
|
|
70
|
+
|
|
71
|
+
pypi-publish:
|
|
72
|
+
name: Publish to PyPI
|
|
73
|
+
needs: build
|
|
74
|
+
runs-on: ubuntu-latest
|
|
75
|
+
environment:
|
|
76
|
+
name: pypi
|
|
77
|
+
url: https://pypi.org/p/chancel
|
|
78
|
+
permissions:
|
|
79
|
+
id-token: write
|
|
80
|
+
steps:
|
|
81
|
+
- name: Download distributions
|
|
82
|
+
uses: actions/download-artifact@v4
|
|
83
|
+
with:
|
|
84
|
+
name: dist
|
|
85
|
+
path: dist/
|
|
86
|
+
- name: Publish to PyPI via Trusted Publishing (OIDC)
|
|
87
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
88
|
+
with:
|
|
89
|
+
packages-dir: dist/
|
|
90
|
+
verbose: true
|
|
91
|
+
|
|
92
|
+
github-release:
|
|
93
|
+
name: Create GitHub Release
|
|
94
|
+
needs: build
|
|
95
|
+
runs-on: ubuntu-latest
|
|
96
|
+
permissions:
|
|
97
|
+
contents: write
|
|
98
|
+
steps:
|
|
99
|
+
- uses: actions/checkout@v4
|
|
100
|
+
- name: Download distributions
|
|
101
|
+
uses: actions/download-artifact@v4
|
|
102
|
+
with:
|
|
103
|
+
name: dist
|
|
104
|
+
path: dist/
|
|
105
|
+
- name: Download demo matrix
|
|
106
|
+
uses: actions/download-artifact@v4
|
|
107
|
+
with:
|
|
108
|
+
name: demo-matrix
|
|
109
|
+
path: .
|
|
110
|
+
- name: Create GitHub Release
|
|
111
|
+
uses: softprops/action-gh-release@v2
|
|
112
|
+
with:
|
|
113
|
+
files: dist/*
|
|
114
|
+
body_path: demo-matrix.md
|
|
115
|
+
generate_release_notes: true
|
chancel-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.pyc
|
|
3
|
+
.venv/
|
|
4
|
+
dist/
|
|
5
|
+
build/
|
|
6
|
+
*.egg-info/
|
|
7
|
+
.pytest_cache/
|
|
8
|
+
.coverage
|
|
9
|
+
coverage.xml
|
|
10
|
+
htmlcov/
|
|
11
|
+
.mypy_cache/
|
|
12
|
+
.ruff_cache/
|
|
13
|
+
mutants/
|
|
14
|
+
.mutmut-cache
|
|
15
|
+
site/
|
|
16
|
+
.env
|
|
17
|
+
.DS_Store
|
|
18
|
+
out/
|
|
19
|
+
|
|
20
|
+
# Synthetic corpora are generated, never committed; the generator lives in scripts/.
|
|
21
|
+
# Ignore the directory's contents (data/*) rather than the directory itself, so the
|
|
22
|
+
# tracked placeholder below survives -- git cannot re-include a file under an
|
|
23
|
+
# excluded directory.
|
|
24
|
+
data/*
|
|
25
|
+
!data/README.md
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.16.3
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff-check
|
|
6
|
+
- id: ruff-format
|
|
7
|
+
- repo: https://github.com/gitleaks/gitleaks
|
|
8
|
+
rev: v8.24.3
|
|
9
|
+
hooks:
|
|
10
|
+
- id: gitleaks
|
|
11
|
+
- repo: https://github.com/compilerla/conventional-pre-commit
|
|
12
|
+
rev: v4.0.0
|
|
13
|
+
hooks:
|
|
14
|
+
- id: conventional-pre-commit
|
|
15
|
+
stages: [commit-msg]
|
|
16
|
+
- repo: local
|
|
17
|
+
hooks:
|
|
18
|
+
- id: no-ai-attribution
|
|
19
|
+
name: forbid AI attribution trailers in commit message
|
|
20
|
+
language: pygrep
|
|
21
|
+
entry: '(?i)(co-authored-by:.*claude|generated with.*claude|anthropic\.com/claude)'
|
|
22
|
+
stages: [commit-msg]
|
|
23
|
+
- id: cassette-secret-scan
|
|
24
|
+
name: scan cassettes for key-shaped strings
|
|
25
|
+
language: pygrep
|
|
26
|
+
entry: '(sk-[A-Za-z0-9_-]{20,}|sk-ant-[A-Za-z0-9_-]{20,}|Bearer [A-Za-z0-9_.-]{20,})'
|
|
27
|
+
files: ^tests/cassettes/
|
|
28
|
+
default_install_hook_types: [pre-commit, commit-msg]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/), and the project adheres to
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-08-19
|
|
10
|
+
|
|
11
|
+
First public release. A demonstration, not a product — see the README's
|
|
12
|
+
out-of-scope list.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- `PolicyGate.authorize()` — the provider-neutral wall every retrieval passes
|
|
16
|
+
through; fails closed on unknown scope, malformed input, resolver failure,
|
|
17
|
+
timeout, empty allowlist, and any resolver attempt to widen authority beyond
|
|
18
|
+
the active scope. 100% statement coverage; mutation-tested to two documented
|
|
19
|
+
equivalent survivors.
|
|
20
|
+
- Domain model (`Scope`, `DocumentId`, `ActiveScope`, `RetrievalReceipt`) whose
|
|
21
|
+
types make a cross-space read unrepresentable under the isolated layout.
|
|
22
|
+
- Three store layouts behind one interface: `isolated` (the claim), `filtered`
|
|
23
|
+
(the vendor-default payload filter), and `shared` (prompt-only), with an
|
|
24
|
+
`inmemory` reference implementation and a `qdrant` local-mode adapter.
|
|
25
|
+
- Provider adapters `anthropic`, `openai_compat`, `echo`, and the offline
|
|
26
|
+
adversarial `hostile_echo`; embedders `hash_stub`, `fastembed_local`, and
|
|
27
|
+
`openai_compat`. Nothing above the adapter boundary names a provider.
|
|
28
|
+
- `Retriever` — the sole caller of a store's search, exposing no collection or
|
|
29
|
+
filter parameter — and a neutral `ScopedAgent` loop whose one retrieval tool
|
|
30
|
+
takes a query string only.
|
|
31
|
+
- Append-only audit log with a SHA-256 hash chain; `chancel verify-log` names
|
|
32
|
+
the first divergent line and prints the head hash as an external anchor.
|
|
33
|
+
- Three-tier memory with `promote_fact()`, which refuses any fact whose
|
|
34
|
+
provenance includes a space-scoped id.
|
|
35
|
+
- The leak suite: seven findings asserting the predicted color per backend, and
|
|
36
|
+
`chancel demo` rendering the backend × provider matrix with no API key, no
|
|
37
|
+
Docker, and no model download.
|
|
38
|
+
- CLI: `demo`, `ask`, `attack`, `verify-log`, `gen-corpus`.
|
|
39
|
+
|
|
40
|
+
[Unreleased]: https://github.com/Nobel-Co/chancel/compare/v0.1.0...HEAD
|
|
41
|
+
[0.1.0]: https://github.com/Nobel-Co/chancel/releases/tag/v0.1.0
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
- PRs only.
|
|
4
|
+
- Use Conventional Commits.
|
|
5
|
+
- No AI attribution trailers in commit messages.
|
|
6
|
+
- Run `just all` before opening a PR.
|
|
7
|
+
- **Any PR containing real data (client names, real matters, real filings) is rejected — synthetic generated data only.**
|
|
8
|
+
- New adapters must pass `tests/conformance/` without modifying it.
|