guardspine-local-council 2.0.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.
Files changed (40) hide show
  1. guardspine_local_council-2.0.0/.github/CLA.md +41 -0
  2. guardspine_local_council-2.0.0/.github/workflows/ci.yml +26 -0
  3. guardspine_local_council-2.0.0/.github/workflows/cla.yml +34 -0
  4. guardspine_local_council-2.0.0/.github/workflows/codeguard.yml +26 -0
  5. guardspine_local_council-2.0.0/.gitignore +10 -0
  6. guardspine_local_council-2.0.0/LICENSE +100 -0
  7. guardspine_local_council-2.0.0/NOTICE +16 -0
  8. guardspine_local_council-2.0.0/PKG-INFO +295 -0
  9. guardspine_local_council-2.0.0/README.md +281 -0
  10. guardspine_local_council-2.0.0/audit-all-repos.py +314 -0
  11. guardspine_local_council-2.0.0/evidence-packs/audit-summary.json +46 -0
  12. guardspine_local_council-2.0.0/evidence-packs/audit.log +146 -0
  13. guardspine_local_council-2.0.0/evidence-packs/council-audit-2026-01-31-run2.json +1122 -0
  14. guardspine_local_council-2.0.0/evidence-packs/guardspine-adapter-webhook-evidence.json +798 -0
  15. guardspine_local_council-2.0.0/evidence-packs/guardspine-kernel-evidence.json +364 -0
  16. guardspine_local_council-2.0.0/evidence-packs/guardspine-local-council-evidence.json +1122 -0
  17. guardspine_local_council-2.0.0/evidence-packs/guardspine-spec-evidence.json +586 -0
  18. guardspine_local_council-2.0.0/evidence-packs/guardspine-verify-evidence.json +1046 -0
  19. guardspine_local_council-2.0.0/evidence-packs/n8n-nodes-guardspine-evidence.json +887 -0
  20. guardspine_local_council-2.0.0/evidence-packs/rlm-docsync-evidence.json +1021 -0
  21. guardspine_local_council-2.0.0/evidence-test.py +201 -0
  22. guardspine_local_council-2.0.0/examples/basic_review.py +45 -0
  23. guardspine_local_council-2.0.0/lib/pii-shield.wasm +0 -0
  24. guardspine_local_council-2.0.0/pyproject.toml +19 -0
  25. guardspine_local_council-2.0.0/src/guardspine_local_council/__init__.py +39 -0
  26. guardspine_local_council-2.0.0/src/guardspine_local_council/adapters/pii_wasm_client.py +129 -0
  27. guardspine_local_council-2.0.0/src/guardspine_local_council/aggregator.py +48 -0
  28. guardspine_local_council-2.0.0/src/guardspine_local_council/council.py +827 -0
  29. guardspine_local_council-2.0.0/src/guardspine_local_council/providers/__init__.py +24 -0
  30. guardspine_local_council-2.0.0/src/guardspine_local_council/providers/anthropic.py +111 -0
  31. guardspine_local_council-2.0.0/src/guardspine_local_council/providers/hooks.py +252 -0
  32. guardspine_local_council-2.0.0/src/guardspine_local_council/providers/mcp_client.py +149 -0
  33. guardspine_local_council-2.0.0/src/guardspine_local_council/providers/ollama.py +102 -0
  34. guardspine_local_council-2.0.0/src/guardspine_local_council/providers/openai.py +96 -0
  35. guardspine_local_council-2.0.0/src/guardspine_local_council/providers/openrouter.py +109 -0
  36. guardspine_local_council-2.0.0/src/guardspine_local_council/types.py +175 -0
  37. guardspine_local_council-2.0.0/tests/__init__.py +0 -0
  38. guardspine_local_council-2.0.0/tests/test_aggregator.py +67 -0
  39. guardspine_local_council-2.0.0/tests/test_council.py +234 -0
  40. guardspine_local_council-2.0.0/tests/test_council_mock.py +128 -0
@@ -0,0 +1,41 @@
1
+ # GuardSpine Individual Contributor License Agreement (DRAFT - counsel review)
2
+
3
+ Purpose: every contribution after this point must grant GuardSpine the right to
4
+ relicense, so the no-CLA gap (the root cause of the aragossa problem) never
5
+ recurs. Based on the Apache ICLA with an explicit relicense/sublicense grant.
6
+ Wire a CLA-assistant bot (e.g., CLA Assistant) to enforce on new PRs.
7
+
8
+ ------------------------------------------------------------------------------
9
+ GUARDSPINE INDIVIDUAL CONTRIBUTOR LICENSE AGREEMENT (v1)
10
+ ------------------------------------------------------------------------------
11
+
12
+ Thank you for contributing to GuardSpine, Inc. ("Company"). This Agreement sets
13
+ the terms under which You contribute. "Contribution" means any work of authorship
14
+ You intentionally submit to a Company project.
15
+
16
+ 1. Copyright license. You grant Company and recipients of software distributed by
17
+ Company a perpetual, worldwide, non-exclusive, royalty-free, irrevocable
18
+ copyright license to reproduce, prepare derivative works of, publicly display,
19
+ sublicense, distribute, and RELICENSE Your Contributions and derivative works,
20
+ under ANY license terms Company selects, including non-open-source and
21
+ commercial terms.
22
+
23
+ 2. Patent license. You grant Company and recipients a perpetual, worldwide,
24
+ non-exclusive, royalty-free, irrevocable (except as below) patent license to
25
+ make, use, sell, offer to sell, import, and otherwise transfer Your
26
+ Contributions, for patent claims You can license that are necessarily
27
+ infringed by Your Contribution alone or combined with the project. If any
28
+ entity brings patent litigation alleging the Contribution infringes, the
29
+ patent licenses You granted for that Contribution terminate.
30
+
31
+ 3. Originality and rights. You represent each Contribution is Your original
32
+ creation and You are legally entitled to grant the above licenses. If Your
33
+ employer has rights to work You create, You represent You have permission to
34
+ contribute, or Your employer has waived such rights.
35
+
36
+ 4. Third-party material. If You submit work that is not Your original creation,
37
+ You will identify it and its license and any restrictions.
38
+
39
+ 5. No warranty. Contributions are provided "as is", without warranties.
40
+
41
+ You: ____________________ GitHub user: ____________ Date: ______
@@ -0,0 +1,26 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12"]
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+ - name: Install dependencies
21
+ run: |
22
+ pip install guardspine-kernel
23
+ pip install -e .
24
+ pip install pytest pytest-asyncio pytest-cov
25
+ - name: Run tests
26
+ run: pytest tests/ -v --tb=short
@@ -0,0 +1,34 @@
1
+ # CLA Assistant - enforces .github/CLA.md on every PR so the no-CLA gap
2
+ # (root cause of the aragossa/PII-Shield ambiguity) cannot recur.
3
+ #
4
+ # BEFORE THIS WORKS, David must (repo settings, one-time):
5
+ # 1. Create a fine-grained PAT with contents+PRs write, add as secret CLA_PAT.
6
+ # 2. The action stores signatures in-repo at signatures/cla.json on main.
7
+ # path-to-document auto-resolves to this repo via github.repository, so it keeps
8
+ # working after the codeguard-action -> guardspine-code-action rename.
9
+ name: CLA Assistant
10
+ on:
11
+ issue_comment:
12
+ types: [created]
13
+ pull_request_target:
14
+ types: [opened, closed, synchronize]
15
+ permissions:
16
+ actions: write
17
+ contents: write
18
+ pull-requests: write
19
+ statuses: write
20
+ jobs:
21
+ cla:
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - name: CLA Assistant
25
+ if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
26
+ uses: contributor-assistant/github-action@v2.6.1
27
+ env:
28
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29
+ PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_PAT }}
30
+ with:
31
+ path-to-signatures: 'signatures/cla.json'
32
+ path-to-document: '${{ github.server_url }}/${{ github.repository }}/blob/main/.github/CLA.md'
33
+ branch: 'main'
34
+ allowlist: DNYoussef,m1el,aragossa,dependabot[bot],*[bot]
@@ -0,0 +1,26 @@
1
+ name: CodeGuard
2
+ on:
3
+ pull_request:
4
+ types: [opened, synchronize]
5
+
6
+ permissions:
7
+ contents: read
8
+ pull-requests: write
9
+ issues: write
10
+
11
+ jobs:
12
+ analyze:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: DNYoussef/codeguard-action@v1
17
+ id: codeguard
18
+ with:
19
+ github_token: ${{ secrets.GITHUB_TOKEN }}
20
+ openrouter_api_key: ${{ secrets.OPENROUTER_API_KEY }}
21
+ risk_threshold: L3
22
+ - uses: actions/upload-artifact@v4
23
+ if: always()
24
+ with:
25
+ name: evidence-bundle
26
+ path: .guardspine/bundles/
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .env
7
+ .venv/
8
+ .DS_Store
9
+ .pytest_cache/
10
+ htmlcov/
@@ -0,0 +1,100 @@
1
+ Business Source License 1.1
2
+
3
+ License text copyright (c) 2017 MariaDB Corporation Ab, All Rights Reserved.
4
+ "Business Source License" is a trademark of MariaDB Corporation Ab.
5
+
6
+ -----------------------------------------------------------------------------
7
+
8
+ Parameters
9
+
10
+ Licensor: GuardSpine, Inc.
11
+
12
+ Licensed Work: GuardSpine Local Council version 2.0.0.
13
+ The Licensed Work is (c) 2026 GuardSpine, Inc.
14
+
15
+ Additional Use Grant: You may make production use of the Licensed Work if your
16
+ use is non-commercial, or is for evaluation or internal
17
+ testing, or is by an organization with annual gross revenue
18
+ below USD 1,000,000. Any other production use, and any use
19
+ that offers the Licensed Work (or a service whose value
20
+ derives primarily from it) to third parties for a fee,
21
+ requires a commercial license from GuardSpine, Inc.
22
+
23
+ Change Date: Four years from the date each version of the Licensed Work
24
+ is first published.
25
+
26
+ Change License: Apache License, Version 2.0
27
+
28
+ For information about alternative licensing arrangements for the Licensed Work,
29
+ please contact legal@guardspine.ai
30
+
31
+ -----------------------------------------------------------------------------
32
+
33
+ Notice
34
+
35
+ Business Source License 1.1
36
+
37
+ Terms
38
+
39
+ The Licensor hereby grants you the right to copy, modify, create derivative
40
+ works, redistribute, and make non-production use of the Licensed Work. The
41
+ Licensor may make an Additional Use Grant, above, permitting limited production
42
+ use.
43
+
44
+ Effective on the Change Date, or the fourth anniversary of the first publicly
45
+ available distribution of a specific version of the Licensed Work under this
46
+ License, whichever comes first, the Licensor hereby grants you rights under the
47
+ terms of the Change License, and the rights granted in the paragraph above
48
+ terminate.
49
+
50
+ If your use of the Licensed Work does not comply with the requirements currently
51
+ in effect as described in this License, you must purchase a commercial license
52
+ from the Licensor, its affiliated entities, or authorized resellers, or you must
53
+ refrain from using the Licensed Work.
54
+
55
+ All copies of the original and modified Licensed Work, and derivative works of
56
+ the Licensed Work, are subject to this License. This License applies separately
57
+ for each version of the Licensed Work and the Change Date may vary for each
58
+ version of the Licensed Work released by Licensor.
59
+
60
+ You must conspicuously display this License on each original or modified copy of
61
+ the Licensed Work. If you receive the Licensed Work in original or modified form
62
+ from a third party, the terms and conditions set forth in this License apply to
63
+ your use of that work.
64
+
65
+ Any use of the Licensed Work in violation of this License will automatically
66
+ terminate your rights under this License for the current and all other versions
67
+ of the Licensed Work.
68
+
69
+ This License does not grant you any right in any trademark or logo of Licensor or
70
+ its affiliates (provided that you may use a trademark or logo of Licensor as
71
+ expressly required by this License).
72
+
73
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN
74
+ "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, EXPRESS
75
+ OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF MERCHANTABILITY, FITNESS
76
+ FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND TITLE.
77
+
78
+ MariaDB hereby grants you permission to use this License's text to license your
79
+ works, and to refer to it using the trademark "Business Source License", as long
80
+ as you comply with the Covenants of Licensor below.
81
+
82
+ Covenants of Licensor
83
+
84
+ In consideration of the right to use this License's text and the "Business Source
85
+ License" name and trademark, Licensor covenants to MariaDB, and to all other
86
+ recipients of the licensed work to be provided by Licensor:
87
+
88
+ 1. To specify as the Change License the GPL Version 2.0 or any later version, or
89
+ a license that is compatible with GPL Version 2.0 or a later version, where
90
+ "compatible" means that software provided under the Change License can be
91
+ included in a program with software provided under GPL Version 2.0 or a later
92
+ version. Licensor may specify additional Change Licenses without limitation.
93
+
94
+ 2. To either: (a) specify an additional grant of rights to use that does not
95
+ impose any additional restriction on the right granted in this License, as the
96
+ Additional Use Grant; or (b) insert the text "None".
97
+
98
+ 3. To specify a Change Date.
99
+
100
+ 4. Not to modify this License in any other way.
@@ -0,0 +1,16 @@
1
+ NOTICE
2
+
3
+ GuardSpine Local Council (c) 2026 GuardSpine, Inc.
4
+ Licensed under the Business Source License 1.1. See LICENSE.
5
+
6
+ This product includes PII-Shield (lib/pii-shield.wasm and integration code),
7
+ authored by aragossa. PII-Shield was contributed under Apache License 2.0.
8
+ A written confirmation that the PII-Shield code and the pii-shield.wasm binary
9
+ may be redistributed under the Business Source License 1.1 is being obtained;
10
+ until then those portions remain available under Apache License 2.0.
11
+
12
+ This product depends on third-party Python packages installed via pip and used
13
+ unmodified, each under its own license, including: PyGithub (LGPL-3.0),
14
+ requests (Apache-2.0), PyYAML (MIT), unidiff (MIT), cryptography (Apache-2.0 /
15
+ BSD-3-Clause), openai (Apache-2.0), anthropic (MIT), wasmtime (Apache-2.0),
16
+ toml (MIT).
@@ -0,0 +1,295 @@
1
+ Metadata-Version: 2.4
2
+ Name: guardspine-local-council
3
+ Version: 2.0.0
4
+ Summary: Local AI council review using Ollama -- no cloud API keys required
5
+ Project-URL: Homepage, https://github.com/DNYoussef/guardspine-local-council
6
+ License-Expression: BUSL-1.1
7
+ License-File: LICENSE
8
+ License-File: NOTICE
9
+ Requires-Python: >=3.10
10
+ Requires-Dist: guardspine-kernel>=0.2.0
11
+ Requires-Dist: httpx>=0.24.0
12
+ Requires-Dist: wasmtime>=16.0.0
13
+ Description-Content-Type: text/markdown
14
+
15
+ # guardspine-local-council
16
+
17
+ **Multi-model AI code review council -- local-first, cloud-optional.**
18
+
19
+ Run multi-model code review councils on your machine using [Ollama](https://ollama.com), or connect to cloud providers (OpenAI, Anthropic, OpenRouter). Reviews produce cryptographically chained evidence bundles compatible with the GuardSpine ecosystem.
20
+
21
+ ## Requirements
22
+
23
+ - Python 3.10+
24
+ - [Ollama](https://ollama.com) running locally (`ollama serve`) for local-only use
25
+ - At least one model pulled (e.g. `ollama pull llama3.1`)
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install guardspine-local-council
31
+ ```
32
+
33
+ Or from source:
34
+
35
+ ```bash
36
+ git clone https://github.com/DNYoussef/guardspine-local-council.git
37
+ cd guardspine-local-council
38
+ pip install -e .
39
+ ```
40
+
41
+ ### Dependencies
42
+
43
+ - **guardspine-kernel** (>=0.2.0) -- Canonical JSON hashing (RFC 8785) and content hash computation. Ensures cross-language parity with the TypeScript `@guardspine/kernel`.
44
+ - **httpx** (>=0.24.0) -- Async HTTP client for Ollama and cloud API calls.
45
+ - **wasmtime** (>=16.0.0) -- WASM runtime for the built-in PII-Shield sanitizer.
46
+
47
+ ## Quick Start
48
+
49
+ ```python
50
+ import asyncio
51
+ from guardspine_local_council import LocalCouncil, OllamaProvider, ReviewRequest
52
+
53
+ async def main():
54
+ providers = [
55
+ OllamaProvider(model="llama3.1", reviewer_id="reviewer-a"),
56
+ OllamaProvider(model="llama3.1", reviewer_id="reviewer-b"),
57
+ OllamaProvider(model="llama3.1", reviewer_id="reviewer-c"),
58
+ ]
59
+
60
+ council = LocalCouncil(providers, quorum=2, consensus_threshold=0.66)
61
+
62
+ request = ReviewRequest(
63
+ artifact_id="my-function",
64
+ artifact_type="python-function",
65
+ content="def add(a, b): return a + b",
66
+ )
67
+
68
+ result = await council.review(request)
69
+ print(f"Decision: {result.consensus_decision} ({result.consensus_confidence})")
70
+
71
+ asyncio.run(main())
72
+ ```
73
+
74
+ ## How It Works
75
+
76
+ 1. You create provider instances (Ollama, OpenAI, Anthropic, or OpenRouter).
77
+ 2. `LocalCouncil` sanitizes the review prompt through the built-in PII-Shield WASM module.
78
+ 3. The sanitized prompt is sent to all providers in parallel.
79
+ 4. Each provider returns a structured vote (approve/reject/abstain + confidence + findings).
80
+ 5. `SimpleAggregator` computes a confidence-weighted majority decision.
81
+ 6. Quorum and consensus threshold checks determine the final result.
82
+ 7. An evidence bundle is produced with a SHA-256 hash chain for tamper detection.
83
+
84
+ ## Providers
85
+
86
+ Four providers are included. All implement the `ReviewProvider` protocol and return `ReviewVote` objects.
87
+
88
+ ### OllamaProvider (local, no API key)
89
+
90
+ ```python
91
+ from guardspine_local_council import OllamaProvider
92
+
93
+ provider = OllamaProvider(
94
+ model="llama3.1", # Any Ollama model
95
+ base_url="http://localhost:11434", # Ollama API endpoint
96
+ reviewer_id="local-1",
97
+ )
98
+ ```
99
+
100
+ Uses Ollama's `/api/generate` endpoint with JSON format mode. Falls back to abstain on parse failure.
101
+
102
+ ### OpenAIProvider
103
+
104
+ ```python
105
+ from guardspine_local_council import OpenAIProvider
106
+
107
+ provider = OpenAIProvider(
108
+ model="gpt-4o",
109
+ api_key="sk-...", # or set OPENAI_API_KEY env var
110
+ )
111
+ ```
112
+
113
+ ### AnthropicProvider
114
+
115
+ ```python
116
+ from guardspine_local_council import AnthropicProvider
117
+
118
+ provider = AnthropicProvider(
119
+ model="claude-sonnet-4-5-20250929",
120
+ api_key="sk-ant-...", # or set ANTHROPIC_API_KEY env var
121
+ )
122
+ ```
123
+
124
+ ### OpenRouterProvider
125
+
126
+ ```python
127
+ from guardspine_local_council import OpenRouterProvider
128
+
129
+ provider = OpenRouterProvider(
130
+ model="openrouter/auto", # or any model on OpenRouter
131
+ api_key="sk-or-...", # or set OPENROUTER_API_KEY env var
132
+ )
133
+ ```
134
+
135
+ You can mix providers freely. A council of 1 Ollama + 1 OpenAI + 1 Anthropic model works.
136
+
137
+ ## Council Configuration
138
+
139
+ | Parameter | Default | Description |
140
+ |-----------|---------|-------------|
141
+ | `providers` | (required) | List of `ReviewProvider` instances |
142
+ | `hooks` | `[]` | Optional list of `ReviewHook` instances for pre/post processing |
143
+ | `sanitizer` | `None` | Optional external sanitizer (in addition to built-in WASM) |
144
+ | `quorum` | `3` | Minimum non-abstain votes required |
145
+ | `consensus_threshold` | `0.66` | Minimum weighted confidence for a decision |
146
+ | `sanitization_salt_fingerprint` | `sha256:00000000` | Non-secret salt fingerprint for sanitization attestations |
147
+
148
+ ## Review Modes
149
+
150
+ ### Single Review
151
+
152
+ `council.review(request)` sends one prompt to all providers in parallel and returns a `CouncilResult` with the aggregated decision.
153
+
154
+ ### Rubric Review
155
+
156
+ `council.rubric_review(request, rubric)` reviews code against a specific rubric. Providers run sequentially (VRAM constraint for local models). Returns a list of `ReviewVote` objects.
157
+
158
+ ```python
159
+ from guardspine_local_council import RubricContext
160
+
161
+ rubric = RubricContext(
162
+ rubric_name="input-validation",
163
+ description="All user input must be validated before use",
164
+ violations=[ # from a deterministic scanner
165
+ {"severity": "high", "rule_id": "IV-001", "file": "api.py", "line_number": 42,
166
+ "description": "Unvalidated query parameter"}
167
+ ],
168
+ )
169
+
170
+ votes = await council.rubric_review(request, rubric)
171
+ ```
172
+
173
+ ### Full Audit
174
+
175
+ `council.full_audit(request, rubrics)` runs all providers against all rubrics and aggregates into an `AuditResult`. Each rubric gets a pass/fail/needs-review verdict via 2-of-3 majority. The overall decision rejects if any rubric with critical findings fails.
176
+
177
+ ```python
178
+ result = await council.full_audit(request, rubrics)
179
+ print(result.overall_decision) # "approve" | "reject" | "needs-review"
180
+ print(result.summary)
181
+
182
+ # Pivot findings from rubric-oriented to file-oriented
183
+ for filename, report in result.by_file().items():
184
+ print(f"{filename}: {report.critical_count} critical findings")
185
+ ```
186
+
187
+ ## Hooks
188
+
189
+ Hooks run deterministically around review calls. The models never call MCP tools themselves -- hooks enrich prompts before the model sees them and validate output after.
190
+
191
+ ### SequentialThinkingHook
192
+
193
+ Connects to `@modelcontextprotocol/server-sequential-thinking` via stdio. Decomposes each rubric into 5 structured reasoning steps and prepends a chain-of-thought scaffold to the prompt.
194
+
195
+ ```python
196
+ from guardspine_local_council import SequentialThinkingHook
197
+
198
+ hook = SequentialThinkingHook(num_steps=5)
199
+
200
+ council = LocalCouncil(providers, hooks=[hook])
201
+ await council.start_hooks()
202
+ result = await council.full_audit(request, rubrics)
203
+ await council.close_hooks()
204
+ ```
205
+
206
+ ### MCPClientHook
207
+
208
+ Generic hook that calls any MCP server's tool to enrich prompts. Useful for injecting library docs, past findings, or external context.
209
+
210
+ ```python
211
+ from guardspine_local_council import MCPClientHook
212
+
213
+ hook = MCPClientHook(
214
+ name="memory",
215
+ server_command=["python", "-m", "memory_mcp"],
216
+ tool_name="recall",
217
+ )
218
+ ```
219
+
220
+ ## PII-Shield Integration
221
+
222
+ All review prompts pass through a built-in PII-Shield WASM module (`lib/pii-shield.wasm`) before reaching any model. This strips API keys, credentials, and PII from code submitted for review.
223
+
224
+ The WASM module runs via `wasmtime` using temporary files for stdin/stdout. The Engine and Module are cached as a singleton; only the Store is recreated per call.
225
+
226
+ **Fail-closed by default**: if the WASM module fails, the review raises `RuntimeError` rather than sending unsanitized content. Set `GUARDSPINE_PII_FAIL_OPEN=1` to override (not recommended for production).
227
+
228
+ You can also provide an additional external sanitizer via the `sanitizer` parameter on `LocalCouncil`. Both stages are tracked in the evidence bundle's `sanitization` attestation block.
229
+
230
+ ## Evidence Bundle Output
231
+
232
+ Council reviews produce v0.2.x evidence bundles containing:
233
+
234
+ - Individual reviewer votes with confidence scores
235
+ - Consensus decision and rationale
236
+ - SHA-256 hash chain with immutability proof (using `guardspine-kernel` for canonical JSON hashing)
237
+ - Optional `sanitization` attestation metadata (v0.2.1 format when sanitization occurred)
238
+
239
+ ```python
240
+ result = await council.review(request)
241
+ bundle = result.evidence_bundle
242
+
243
+ print(f"Bundle ID: {bundle.bundle_id}")
244
+ print(f"Version: {bundle.version}") # "0.2.0" or "0.2.1"
245
+ print(f"Root hash: {bundle.immutability_proof.root_hash}")
246
+
247
+ # Verify with guardspine-verify
248
+ import json
249
+ with open("council-evidence.json", "w") as f:
250
+ json.dump(bundle.__dict__, f, default=str)
251
+ # $ guardspine-verify council-evidence.json
252
+ ```
253
+
254
+ Bundles are unsigned by default. For signed bundles, use GuardSpine Enterprise or provide a signing key via configuration.
255
+
256
+ ## Ollama Setup
257
+
258
+ ```bash
259
+ # Check Ollama is running
260
+ curl http://localhost:11434/api/tags
261
+
262
+ # Pull models
263
+ ollama pull llama3.1
264
+ ollama pull codellama
265
+ ```
266
+
267
+ The council returns abstain votes if a model is unavailable.
268
+
269
+ ## Data Types
270
+
271
+ | Type | Purpose |
272
+ |------|---------|
273
+ | `ReviewRequest` | Input: artifact ID, type, content, optional context and risk tier hint |
274
+ | `ReviewVote` | One reviewer's decision (approve/reject/abstain), confidence, rationale, findings |
275
+ | `CouncilResult` | Aggregated result with votes, consensus, dissent, quorum status, evidence bundle |
276
+ | `RubricContext` | Scanner-produced rubric with name, description, and violations |
277
+ | `RubricVerdict` | Per-rubric result: pass/fail/needs-review with critical findings |
278
+ | `AuditResult` | Full audit result across all rubrics with overall decision |
279
+ | `FileFinding` | Single finding attributed to a specific file (for `by_file()` pivot) |
280
+ | `FileReport` | All findings for one file, with `critical_count` property |
281
+ | `EvidenceBundle` | v0.2.x bundle with items, hash chain, and optional sanitization |
282
+
283
+ ## Related Projects
284
+
285
+ | Project | Description |
286
+ |---------|-------------|
287
+ | [guardspine-kernel-py](https://github.com/DNYoussef/guardspine-kernel-py) | Python kernel: canonical hashing, content hash (required dependency) |
288
+ | [@guardspine/kernel](https://github.com/DNYoussef/guardspine-kernel) | TypeScript kernel (cross-language parity) |
289
+ | [guardspine-verify](https://github.com/DNYoussef/guardspine-verify) | Verify council evidence bundles offline |
290
+ | [guardspine-spec](https://github.com/DNYoussef/guardspine-spec) | Bundle specification (v0.2.1) |
291
+ | [codeguard-action](https://github.com/DNYoussef/codeguard-action) | GitHub Action for automated code review |
292
+
293
+ ## License
294
+
295
+ Business Source License 1.1 (source-available) -- see [LICENSE](LICENSE). Free for non-commercial, evaluation, and small-organization use (annual revenue under USD 1,000,000); other production use requires a commercial license from GuardSpine, Inc. Each version converts to Apache-2.0 four years after its release.