release: v0.14.1 — SBOM fix + #263 sync auto-bind + #257 diagnose CLI#267
Conversation
…SP-03 + OWASP-05) Plan for OWASP-03 + OWASP-05 bundle. Both reduce to install/update supply-chain posture declarations under the epic's offered "document explicitly" option: - OWASP-03: floor-only stance (uv/pipx authority is the install lock authority; no shipped requirements.lock) - OWASP-05: option-(b) trust-on-first-use posture (TLS + organizational access + operator-side gating; cosign verification deferred to sigstore-python activation, currently stubbed at release/manifest_verify.py:_sigstore_verify) Audit: round 1 PASS (L1, pure-doc). Adversarial-mode A04 scrutiny resolved: documenting residual TLS-only-trust risk explicitly + escape hatch + future-activation path is doctrine-positive (NOT silent fail-open per A04). Closes 2 of 3 remaining #218 sub-tasks. Only LLM-06 / #214 remains after this lands. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ts (#218 OWASP-03 + OWASP-05) `docs/policies/install-trust-model.md` (new) — two-section operator-readable trust-model document: - §Install-time (OWASP-03): declares floor-only stance; uv/pipx as the install-lock authority; pin-install path for reproducibility; what changes for hosted deployment; OWASP A06 cross-reference for SBOM-based dependency audits. - §Update-time (OWASP-05): declares TLS-only active stance for the RECOMMENDED_VERSION fetch; lists what's trusted (TLS, GitHub org controls, 1-hour cache); what's NOT trusted (content-level signature); operator escape hatch (pin install + manually verify wheel); future activation path (sigstore-python verifier + sign-recommended-version workflow + handlers/update.py verifier); OWASP A08 cross-reference. `README.md` Compliance posture section now lists 4 policy files (was 3), plus an explicit pointer to docs/RELEASE_EVIDENCE_PROCEDURE.md for per-release change-control evidence. `docs/research-brief-compliance-audit-2026-05-06.md` OWASP-03 and OWASP-05 entries marked closed by docs/policies/install-trust-model.md (matches Plan D + Plan C bidirectional cross-reference pattern). `tests/test_install_trust_model_doc.py` (new) — 5 functional content- contract tests covering the install-time + update-time section commitments, cross-reference presence, README link, and research brief closure pointers. Per the doctrine interpretation locked in Plan D's audit: the unit IS the doc content; read_text() + assertion is genuine unit invocation. Closes #218 sub-tasks OWASP-03 + OWASP-05. Two more #218 items closed; only LLM-06 / #214 remains in the epic. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…update-trust-model docs(compliance): install + update trust model bundle (#218 OWASP-03 + OWASP-05)
…b-task) Plan for LLM-06 / #214 — the last remaining #218 sub-task. Mirrors #237 LLM-11's hooks-manifest pattern for the skills-content surface. Audit: round 1 VETO infrastructure-mismatch (invalid TOML mixing single-bracketed [X] with double-bracketed [[X]] for the same hatch hooks.custom key + wrong hatch plugin model) → round 2 PASS after collapsing both build hooks into a single registered module. Substrate observations folded into round-2 plan: - LLM-06 framing: substrate REMOVES the design-constraint gate per the brief's framing (line 416); enables future remote-skill-loading without re-litigating signing - Cosign activation timing: inherits #237's deferred sigstore-python stub; both verifiers activate together when sigstore wiring lands - TOML emitter: manual emission with ~30 LOC ceiling acceptance bound (swap to tomli_w if exceeded) Closes #218 sub-task LLM-06 (6 of 6 #218 sub-tasks; epic complete). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three new release modules + 14 functional tests: - release/skills_source.py — single source of truth: walk_skills() yields (skill_name, filename, file_bytes) for every signed-content file under skills/<skill_name>/. Filters to *.md and *.yaml; stray files at root silently skipped. - release/skills_manifest_generator.py — pure-function deterministic TOML manifest writer. SHA-256 over file bytes, hex-encoded. Sorted-key serialization (skills lex-sorted; files within skill lex-sorted). Manual TOML emission stays well under the ~30 LOC ceiling acceptance bound from the round-1 audit substrate observation. - release/skills_verify.py — sigstore-python verifier for the skills manifest. Mirrors release/manifest_verify.py from #237 LLM-11. Stub _sigstore_verify with explicit "deferred follow-up" framing; activates alongside #237's stub when the shared sigstore-python wiring lands. Per-file SHA-256 cross-check refuses on any mismatch. BICAMERAL_SKILLS_VERIFY_DISABLE=1 bypass writes severity-3 verification_bypassed ledger event with manifest_kind="skills" (disambiguates from LLM-11 hooks-manifest bypass events). 14 functional tests: - 7 generator tests (manifest derivation, SHA-256, byte-deterministic serialization, *.txt filtering, root-file skip, tomllib round-trip, lex-ordering) - 7 verifier tests (positive verify, sig-invalid, sha256-mismatch, missing-manifest, swappable verifier hook, bypass-with-event, fail-closed-without-env-var) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…LLM-06) `scripts/hooks_manifest_build_hook.py` — refactored into a single `ManifestsBuildHook` class with two-step `initialize` that generates both hooks-manifest.json AND skills-manifest.toml at wheel-build time. Hatch's `[tool.hatch.build.targets.wheel.hooks.custom]` registers exactly ONE plugin class per module path; consolidating into one class is the correct hatch plugin pattern (round-1 audit caught the original two-class-per-module model as wrong; verified in implementation when hatch refused with "Multiple subclasses of `BuildHookInterface` found"). `pyproject.toml` — extends `[tool.hatch.build.targets.wheel.shared-data]` table with one new entry mapping skills-manifest.toml into the wheel at <sys.prefix>/share/bicameral-mcp/. The single `[tool.hatch.build.targets.wheel.hooks.custom]` registration is unchanged. `setup_wizard.py` — adds `_bundled_skills_manifest_paths()` discovery helper (mirrors `_bundled_manifest_paths`) and `_verify_intended_skills_writes()` helper (mirrors `_verify_intended_writes`). `_install_skills` gets one new LOC: the verify-call before any file copy. Dev installs (no bundled artifacts) get no-op via the discovery returning None — matches the LLM-11 dev/prod gating pattern. Wheel build smoke test verified: post-build wheel ships `bicameral_mcp-*.data/data/share/bicameral-mcp/skills-manifest.toml` at the proper hatch shared-data location. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…218 LLM-06) `.github/workflows/publish.yml` — extends the publish pipeline with two new steps: generate skills-manifest.toml under dist/share/ (mirrors the hooks-manifest generation pattern), then cosign keyless-sign. Outputs (txt + sig + crt) attached to the GitHub Release alongside the hooks-manifest, SBOM, and tag-commit signed artifacts. `docs/policies/host-trust-model.md` — extends the Server-side guarantees table with a row for skills-manifest signature verification. Cross-references LLM-06 / #214 / #218. `docs/research-brief-compliance-audit-2026-05-06.md` — LLM-06 entry marked closed by the new release/skills_* modules + the ManifestsBuildHook skills-manifest generation. Closure pointer explicit about: (a) substrate REMOVES the design-constraint gate enabling future remote-skill-loading, (b) secondary benefit is post-install in-place-tampering detection. `tests/test_compliance_policy_docs.py` — 1 new content-contract test verifying the host-trust-model.md skills-manifest row exists and references _install_skills + skills-manifest.toml + LLM-06. Closes #218 LLM-06 / #214. Epic #218 now COMPLETE (6 of 6 sub-tasks): LLM-11 + OWASP-01 + SOC2-03 + OWASP-03 + OWASP-05 + LLM-06. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…bstantiated Out-of-band substantiation: implementation already merged to dev via PR #249 (merge commit b2fc66e); this ledger-only commit lands the seal entry off dev tip. Closes #214 (#218 sub-task LLM-06 — sign skills/ payload). Closes #218 epic (6/6 sub-tasks complete). Reality matches Promise: 11 planned files committed (3 phases of plan-F); 14 new functional tests (7 generator + 7 verifier; 2 generator tests beyond plan-F enumeration for stronger contract coverage) plus 1 content-contract test extension all PASS. Wheel-build smoke test verified skills-manifest.toml ships at hatch shared-data location alongside hooks-manifest.json. Logged deviation: build-hook consolidated deeper than the audit's Path A — single ManifestsBuildHook class generating both manifests in one initialize, per hatch's actual one-class-per-registered-module constraint. Same auto-discovery semantics; cleaner module structure. Cosign keyless signing of skills-manifest.toml mirrors #237 LLM-11 pipeline; deferred sigstore-python verifier wiring activates both manifests together when it lands. Plan: plan-F-llm-06-skills-manifest-signing.md Audit: round 2 PASS (round-1 infrastructure-mismatch VETO cleared).
…erators (#227) Closes SOC2-06 + OWASP-06 fold per docs/research-brief-compliance-audit-2026-05-06.md § 2.2 + § 2.3. Three phases: - Phase 1: audit_log.py module + JsonFormatter + forbid-list + 22 functional tests - Phase 2: server lifecycle + tool-invocation wrapper + dual-write at ingest refusal / preflight bypass sites + 15 functional tests - Phase 3: operator policy doc + research-brief closure + 1 content-contract test extension + README compliance-posture row Audit: round 1 VETO (specification-drift between dual-write helper code and test specs requiring bidirectional exception independence) -> round 2 PASS after applying Path A (wrap each call in try/except for bidirectional exception isolation; helper-extraction guidance for preflight bypass site).
… Phase 1) Closes SOC2-06 + OWASP-06 fold from docs/research-brief-compliance-audit-2026-05-06.md § 2.2 + § 2.3. audit_log.py (240 LOC) provides the operator-facing structured-log emission surface. Distinct from preflight_telemetry.py's machine-join JSONL writers and telemetry.py's PostHog outbound; this is the incident-readability surface. Channel: stderr-default; BICAMERAL_AUDIT_LOG=<path> override; =disabled no-op; unwriteable path falls back to stderr with one warning marker. Level: BICAMERAL_AUDIT_LOG_LEVEL=info|warn|error filters event types via _LEVEL_BY_EVENT table. Forbid-list: frozenset of 10 known content-bearing keys (decision_text, file_paths, transcript, arguments, payload, content, text, body, output, result_text); forbidden_keys_stripped field surfaces redactions to the operator without exposing content. 22 functional tests pass (6 forbid-list + 8 format + 8 channel). Each test invokes the unit and asserts on returned value or observable side-effect (captured stderr, parsed JSON line, file contents). Per qor/references/doctrine-test-functionality.md. Plan: plan-227-structured-audit-log-emission.md Audit: round 1 VETO specification-drift -> round 2 PASS via Path A (bidirectional exception isolation in dual-write helper, scoped to Phase 2).
…227 Phase 2) server.py: - serve_stdio() emits SERVER_START at entry; SERVER_SHUTDOWN in finally - @server.call_tool() outer wrapper (was the existing impl) extracted to _call_tool_impl; new outer dispatcher captures tool_name + duration_ms + outcome_class (ok/refused/error) via try/except/finally, emits TOOL_INVOCATION on every dispatch regardless of return path - arguments dict never enters the audit-log emit (forbid-list locked at the call site by construction; only session_id is extracted) context.py: - _emit_config_load_once() helper with module-level idempotent guard; invoked by BicameralContext.from_env() after instance construction - emits CONFIG_LOAD with ingest_max_bytes / ingest_rate_limit_burst / ingest_rate_limit_refill_per_sec / guided_mode (int/bool only; no path-bearing fields sourced into the emit) handlers/ingest.py: - _emit_ingest_refusal_telemetry amended to dual-write; each call (preflight_telemetry.write_ingest_refusal_event + audit_log.emit) wrapped in independent try/except so failure of either MUST NOT block the other; original _IngestRefused propagates cleanly via caller's raise DEVIATION (logged): plan-227 Phase 2 also called for dual-write at the preflight bypass site. The v1 bypass surface is reverted (commit d1e3914) so preflight_telemetry.write_bypass_event has no active caller to wire. Test test_preflight_bypass_writes_both_jsonl_and_audit_log dropped from this implementation; when the v1 surface returns (separate work), the same dual-write helper pattern from handlers.ingest._emit_ingest_refusal_telemetry becomes the template. Documented in tests/test_audit_log_dual_write.py module docstring. Tests: 14 functional tests pass (4 server lifecycle + 7 tool invocation + 3 ingest dual-write). Full ingest-gate regression: 114 sister tests pass (canary + size + rate + sensitive + server-refusal). No regressions introduced. Plan: plan-227-structured-audit-log-emission.md Audit: round 2 PASS
…hase 3) docs/policies/audit-log.md (new) — operator-readable policy: - Channel resolution table (BICAMERAL_AUDIT_LOG values vs behavior) - Level filter table (info/warn/error event coverage) - Closed event taxonomy with per-class fields - Forbid-list discipline + symmetry with telemetry.py - Failure semantics (POSIX-syslog precedent) - Dual-write rationale at gate sites - Integration patterns (logrotate, journalctl, file collectors) - Log retention guidance for SOC 2 CC operators - v1 out-of-scope items docs/research-brief-compliance-audit-2026-05-06.md: - SOC2-06 (line 316): Status closed by audit_log.py + policy doc - OWASP-06 (line 355): Status closed (folded with SOC2-06) README.md: "Compliance posture" section bumped from 3 -> 4 policy files; new audit-log.md row added. tests/test_compliance_policy_docs.py: - test_audit_log_policy_doc_includes_channel_resolution_table — content-contract test locking the channel-resolution table presence + SOC2-06/OWASP-06 cross-reference - test_audit_log_policy_doc_documents_event_taxonomy — locks bidirectional drift between AuditEventType enum and the operator- facing event taxonomy table Tests: 8 content-contract tests pass (6 existing + 2 new). Full #227 regression: 150 tests pass (28 audit_log + 8 compliance docs + 114 ingest sister-suites). Plan: plan-227-structured-audit-log-emission.md Audit: round 2 PASS
…ated Closes SOC2-06 (SOC 2 CC system-monitoring gap) and OWASP-06 (OWASP A09 logging gap) per docs/research-brief-compliance-audit-2026-05-06.md § 2.2 line 316 + § 2.3 line 355. Reality matches Promise: 13 planned files (1 new module + 6 new test files + 4 modified source files + 2 docs) shipped across 3 phases. 38 new functional tests pass; 114 ingest-gate sister regression tests pass clean; 8 compliance content-contract tests pass. Full repo regression: 1038 pass with 10 baseline-confirmed pre-existing failures in untouched modules (zero net new regressions). Logged deviation: preflight bypass dual-write deferred — v1 surface reverted (commit d1e3914) so write_bypass_event has no active caller in v0; helper pattern from handlers.ingest._emit_ingest_refusal_telemetry becomes template when v1 surface returns. Captured in tests/test_audit_log_dual_write.py docstring. Audit: round 1 VETO specification-drift (dual-write helper code contradicted bidirectional-independence test specs) -> round 2 PASS via Path A (wrap each write in independent try/except). Plan: plan-227-structured-audit-log-emission.md
feat(audit-log): structured audit-log emission for self-hosted operators (#227)
… Layer 1) Closes #252 Issue 2 Layer 1 per docs/research-brief-252-privacy-preserving-ledger-remediation.md. Root cause class: "Invalid revision N for type Value" deserialization errors from SurrealDB are wire-format version mismatches between the surrealdb-py client and the on-disk SurrealKV record headers. With `surrealdb>=2.0.0` (floor only), a routine `pip install --upgrade` can swap the lib under an existing ledger and silently introduce incompatibility — the failure mode that hit `jaune24` (#252). Patch-level pin to ==2.0.0 is unambiguous (surrealdb 2.x has only one patch published; future patches must verify the wire format and ship with a deliberate migration path). Future surrealdb bumps require: - Schema-revision sentinel persisted in the ledger (#252 Layer 2) - Operator-driven export/import (#252 Layer 4) for migration - Explicit pin update in this file with rationale This is privacy-preserving by construction — no customer data needed to ship the fix. Plan: docs/research-brief-252-privacy-preserving-ledger-remediation.md
…at awareness (#252) Closes #252 Issue 2 Layer 2 per docs/research-brief-252-privacy-preserving-ledger-remediation.md. Two phases: - Phase 1: extend schema_meta with surrealdb_client_version_at_first_write + at_last_write + last_write_at fields; add _write_wire_format_sentinel helper invoked from init_schema; bump SCHEMA_VERSION to 16; add no-op _migrate_v15_to_v16 to keep the registry happy on existing v15 ledgers. - Phase 2: add LEDGER_SCHEMA_VERIFIED + LEDGER_VERSION_DRIFT to the AuditEventType enum + their level mapping. WARN-only posture per operator directive ("gating is observability, not obstruction"); the drift event lights up the surface but does NOT fail-fast — fail-fast belongs in Layer 5 where it pairs with a migration path. ~120 LOC + 11 functional tests across Phase 1 + 2. No new pip deps. Layer 4's operator-facing policy-doc update for the new events is intentionally deferred (avoids two-PR doc churn for the same surface). Plan: plan-252-layer-2-schema-revision-sentinel.md Strategy: docs/research-brief-252-privacy-preserving-ledger-remediation.md
…a table) (#252) Round 1 VETO: infrastructure-mismatch (3 binding findings): 1. schema_meta is DELETEd on every _set_schema_version call (line 924-930); extending it for wire-format sentinel would wipe data on every migration. 2. client.query(sql, vars=None) — keyword is `vars`, not `bindings`. 3. init_schema() runs BEFORE migrate(), not after; sentinel call timing wrong. Round 2 amendments (Path B per audit recommendation): 1. New dedicated `bicameral_meta` table (separate from `_META`/schema_meta); _BICAMERAL_META constant wired into init_schema's define-pass loop. 2. _write_wire_format_sentinel queries/updates `bicameral_meta` (clean from DELETE+CREATE concerns); uses positional `vars` dict matching the existing `_set_schema_version` convention. 3. Sentinel call moves to `adapter.connect()` AFTER both init_schema() and migrate() return successfully; on DestructiveMigrationRequired, the existing exception handler short-circuits and the sentinel is skipped (destructive-migration-pending warning is the higher-priority signal). Round 1 advisory (drop xfail): - Layer 2 NOW updates docs/policies/audit-log.md event-taxonomy table inline so the existing #227 test_audit_log_policy_doc_documents_event_taxonomy content-contract test stays green. No more deferred-doc churn argument. ~150 LOC + 13 functional tests across Phase 1 + 2. No new pip deps. Implementer notes lock the option<datetime> smoke-test concern + the two-layer try/except discipline in adapter.connect(). Plan: plan-252-layer-2-schema-revision-sentinel.md (round 2) Strategy: docs/research-brief-252-privacy-preserving-ledger-remediation.md
…er 2) Closes #252 Layer 2 per docs/research-brief-252-privacy-preserving-ledger-remediation.md. ledger/schema.py: - New _BICAMERAL_META constant defining bicameral_meta table with surrealdb_client_version_at_first_write (option<string>), surrealdb_client_version_at_last_write (option<string>), and last_write_at (option<datetime>) fields. Wired into init_schema's define-pass loop alongside _META. - New _write_wire_format_sentinel(client) helper that reads the recorded surrealdb-py version, updates last-write fields with importlib.metadata.version("surrealdb"), preserves at_first_write immutability after first set, and returns (recorded, running, status) where status ∈ {"first-write", "match", "drift"}. - New _migrate_v15_to_v16 (no-op body; registry entry only) — the bicameral_meta DEFINEs are applied via init_schema's OVERWRITE pass; the registry entry exists so SchemaVersionTooNew is not raised on v15 ledgers transitioning to v16. - SCHEMA_VERSION bumped 15 → 16; SCHEMA_COMPATIBILITY entry added. ledger/adapter.py: - New _emit_wire_format_sentinel() helper extracted from connect() per Razor advisory; called at the end of connect()'s success path (after _connected = True). Two-layer try/except: outer wraps _write_wire_format_sentinel raise; inner wraps audit_log.emit raise. Both required by the existing audit_log discipline ("audit log MUST NOT break callers"). On DestructiveMigrationRequired, the existing early-return correctly skips the sentinel. audit_log.py: - Two new AuditEventType values: LEDGER_SCHEMA_VERIFIED (info-level, fires on first-write or match) + LEDGER_VERSION_DRIFT (warn-level, fires on mismatch). Per operator directive ("gating is observability, not obstruction"), drift WARNs but does NOT fail-fast — fail-fast belongs in Layer 5 where it pairs with a migration path. docs/policies/audit-log.md: - Event-taxonomy table extended with rows for both new event types. Locks the existing #227 test_audit_log_policy_doc_documents_event_taxonomy content-contract test (which would have failed on Phase 2's enum extension without this update). Tests: 21 new functional tests (10 sentinel-helper + 4 adapter-connect integration + 3 migration + 4 audit-log event-type + 1 doc/code drift lock) — all PASS. Ledger + audit_log sister regression: 108 tests pass clean. option<datetime> smoke-tested against memory:// fixture before relying on it (per implementer note); SurrealDB v2 embedded supports the form, so the planned fallback to non-option datetime was not needed. Plan: plan-252-layer-2-schema-revision-sentinel.md Audit: round 1 VETO infrastructure-mismatch -> round 2 PASS via Path B (separate bicameral_meta table; positional vars dict; sentinel call at adapter.connect post-init+migrate; policy-doc inline update).
…tantiated Closes #252 Layer 2 per docs/research-brief-252-privacy-preserving-ledger-remediation.md. Reality matches Promise: 7 planned files (3 new tests + 4 modified sources/docs) shipped across 2 phases. 21 new functional tests pass (8 more than the plan's 13 — doctrine-positive expansion covering PackageNotFoundError fallback, partial-row edge cases, and helper- isolation paths). Ledger + audit_log + compliance_policy sister regression: 108 tests pass clean. Logged deviations: 1. _emit_wire_format_sentinel helper extraction in adapter.py per Razor advisory (non-VETO). Mirrors _emit_ingest_refusal_telemetry from #227. Doctrine-positive; no behavior change. 2. Test count expansion (13 -> 21) for edge-case coverage. option<datetime> smoke-tested against memory:// fixture; works in SurrealDB v2 embedded — fallback to non-option datetime not needed. Audit: round 1 VETO infrastructure-mismatch (3 binding: schema_meta DELETEd on every migration; bindings kwarg doesn't exist; init_schema runs before migrate) -> round 2 PASS via Path B (separate bicameral_meta table; positional vars dict; sentinel call at adapter.connect post-init+migrate; policy-doc inline update). Plan: plan-252-layer-2-schema-revision-sentinel.md Strategy: docs/research-brief-252-privacy-preserving-ledger-remediation.md
fix(deps): pin surrealdb==2.0.0 — close wire-format drift surface (#252 Layer 1)
…sion-sentinel feat(ledger): #252 Layer 2 — wire-format sentinel via bicameral_meta table
Privacy-preserving operator bug-report tool per docs/research-brief-252-privacy-preserving-ledger-remediation.md. Three phases: - Phase 1: Diagnosis dataclass + _ALLOWED_FIELDS frozenset + async gather_diagnosis() with private readers (ledger metadata, bicameral_meta sentinel, table counts, audit-log tail merge across both JSONL + optional configured path, suggestion engine). Allowlist-by-field-name is the load-bearing privacy mechanism. - Phase 2: format_diagnosis() markdown renderer + CLI subparser registration in server._register_subparsers + dispatch arm. Output is operator-pasteable markdown with 6 section headers. - Phase 3: operator policy doc enumerating allowlisted fields + suggestion heuristic catalog; content-contract tests lock doc/code drift; README compliance-posture row added. ~280 LOC + 25 functional tests. No new pip deps. Implementation can land before #256 merges (bicameral_meta reads are tolerant of missing table); integration test for first-write status xfail-marked until #256 reaches dev. Five hardcoded suggestion heuristics: drift detected; recommended- version mismatch; audit-log disabled; ledger > 100 MiB; schema version old. Plugin extension is YAGNI for v1. Audit-log tail covers BOTH ~/.bicameral/preflight_events.jsonl AND BICAMERAL_AUDIT_LOG=<path> when configured (full-utility hybrid per operator directive — refused the simplification tradeoff). Plan: plan-252-layer-3-diagnose-cli.md Strategy: docs/research-brief-252-privacy-preserving-ledger-remediation.md Depends on: #252 Layer 2 (#256) for bicameral_meta table availability
…eport (#252 Layer 3) Closes #252 Layer 3 per docs/research-brief-252-privacy-preserving-ledger-remediation.md. cli/diagnose.py (215 LOC): Diagnosis dataclass + _ALLOWED_FIELDS frozenset (17 fields) + _CANONICAL_TABLES + format_diagnosis() markdown renderer + main() entrypoint. Allowlist-by-field-name is the load-bearing privacy mechanism. cli/_diagnose_gather.py (244 LOC, helper-extracted per Razor advisory): gather_diagnosis() async function + 5 private readers (ledger metadata, bicameral_meta sentinel, schema_version, table_counts, audit-log tail merge across both preflight_events.jsonl AND BICAMERAL_AUDIT_LOG=<path> when configured) + 5-heuristic suggestion engine. server.py: new "diagnose" subparser registration + dispatch arm. handlers/update.py: public alias `fetch_recommended_version` for clean cross-layer call from cli/diagnose without re-using private underscore symbol. docs/policies/diagnose-output.md: operator-readable allowlist policy + suggestion heuristic catalog + always-safe-to-paste guarantee. README.md: Compliance posture section bumped 5 → 6 policy files. Tests: 31 new functional tests across 4 files (allowlist parity + gather scenarios + format rendering + CLI subprocess) plus 2 new content-contract tests in test_compliance_policy_docs.py (allowlist field doc-coverage + suggestion-catalog drift lock). All 41 PASS. option<datetime> support inherited from #252 Layer 2 (not present on this branch base; bicameral_meta read returns "first-write" status on pre-Layer-2 ledgers — semantically equivalent for diagnostic purposes). Plan: plan-252-layer-3-diagnose-cli.md Audit: round 1 PASS (no VETO — plan absorbed prior-round audit learnings: separate-table architecture from Layer 2; allowlist + content-contract test pattern from #227; helper-extraction acknowledged in implementer notes).
…I substantiated Closes #252 Layer 3 per docs/research-brief-252-privacy-preserving-ledger-remediation.md. Reality matches Promise: 9 planned files (4 new tests + 1 new policy doc + 4 modified sources/docs) shipped across 3 phases. 41 new functional tests pass (16 more than the plan's 25 — doctrine-positive expansion covering per-reader unit boundary + edge cases like PackageNotFoundError unknown branch, recent-events 5-cap enforcement, table-counts canonical subset). Logged deviations: 1. Helper-extraction split per Razor advisory (cli/diagnose.py 213 + cli/_diagnose_gather.py 244, both under 250). Mirrors cli/_link_commit_runner.py pattern. 2. drift_status semantics adjusted to "match" on fresh ledger (Layer 2's sentinel writes row at adapter.connect time; gather sees populated row by the time it runs). Test renamed to reflect Layer-2-integrated reality. 3. Test count expansion (25 -> 41). Privacy posture: allowlist-by-field-name at the Diagnosis dataclass level. Negative content-leak test verifies decision content never appears in repr(Diagnosis). Forbidden-field-name negative lock catches future field expansion using content-bearing names. Three audit advisories all applied: - Razor headroom: helper extraction - Recommended-version fetch: public alias added - CLI tests: network monkeypatched (no live HTTP) Audit: round 1 PASS (no VETO — plan absorbed prior-round audit learnings). Plan: plan-252-layer-3-diagnose-cli.md Strategy: docs/research-brief-252-privacy-preserving-ledger-remediation.md
Updates the requirements on [bm25s](https://github.com/xhluca/bm25s) to permit the latest version. - [Release notes](https://github.com/xhluca/bm25s/releases) - [Commits](xhluca/bm25s@0.2.0...0.3.8) --- updated-dependencies: - dependency-name: bm25s dependency-version: 0.3.8 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
Updates the requirements on [sqlite-vec](https://TODO.com) to permit the latest version. --- updated-dependencies: - dependency-name: sqlite-vec dependency-version: 0.1.9 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com>
feat(cli): #252 Layer 3 — bicameral-mcp diagnose CLI
…-0.3.8 build(deps): update bm25s requirement from >=0.2.0 to >=0.3.8
…c-gte-0.1.9 build(deps): update sqlite-vec requirement from >=0.1.0 to >=0.1.9
- pyproject.toml: 0.13.3 → 0.14.1 (dev was stuck at 0.13.3 throughout the v0.14.0 stream; bumping past v0.14.0 to align with what's actually on main) - RECOMMENDED_VERSION: 0.13.3 → 0.14.1 - pyproject.toml scripts: drop `bicameral-mcp-classify` (broken since #244 deleted cli/classify.py — carryover cleanup from the v0.14.0 release surgery) - release/sbom_emit.py: install wheel into temp venv before scanning. Fixes the v0.14.0 publish-pipeline halt where `cyclonedx-py environment <wheel>` failed because the subcommand introspects a Python environment via a Python-executable path, not a wheel file. New flow: tempdir venv → pip install wheel + cyclonedx-bom → run `cyclonedx-py environment --output-file <out> <venv-python>`. Output is the wheel's actual dependency closure with no contamination from the build env. `--output-file` flag replaces v0.14.0's `-o` short form (cyclonedx-py 7.x dropped the alias). - CHANGELOG.md: new ## v0.14.1 release header summarizing SBOM fix + #257 diagnose CLI + #259/#260 dependabot bumps. Demoted prior "[Unreleased]" content to "[Unreleased — pre-v0.14.0]" to mark the cutoff. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sync's autonomy contract (skills/bicameral-sync/SKILL.md:203 — "Always complete step 2 before responding ... runs autonomously after a commit. Do not wait for user input.") broke whenever a touched decision had no `binds_to` edge yet, which is the common case for any newly-ingested decision. The agent had to be hand-walked: sync → "now bind" → "now sync again". P0 because every onboarding session hits this on the first post-ingest commit, breaking demo 02's headline value claim. Root cause: `handlers/link_commit.py` injects `_GROUNDING_INSTRUCTION_*` strings telling the agent what to do next, but the sync skill's only explicit step (step 2) consumes `pending_compliance_checks`, not `pending_grounding_checks`. Ungrounded entries fell through to "end of skill, return to user." Fix: new step 1.5 between current step 1 (Sync HEAD) and step 2 (Resolve every pending compliance check). The new step: - Triggers on `pending_grounding_checks` entries with `reason="ungrounded"`. - Resolves the symbol via Grep/Read + validate_symbols on the touched file. - Calls `bicameral.bind(decision_id, file_path, symbol_name)`. - Concatenates the returned `PendingComplianceCheck` with any pendings from the original `link_commit` response and proceeds to step 2 in the same invocation — no second human nudge. The `reason="symbol_disappeared"` (relocation) path is preserved unchanged — that case still bails out for V2 atomic rebind, per existing handlers/link_commit.py:77-92 logic. A one-line *symbol* glossary lands at the head of step 1.5, on first use of `symbol_name` in this skill (no separate bicameral-bind skill file exists; bind is invoked from sync + ingest, with the glossary introduced wherever the first invocation lives). Acceptance per #263: - [x] sync skill has explicit step consuming `pending_grounding_checks` reason=ungrounded → emits bind call - [x] After bind, skill proceeds to compliance resolution in same invocation (concatenated pendings list) - [x] reason=symbol_disappeared path unchanged - [x] Symbol glossary line in skill Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New workflow `.github/workflows/notify-slack-on-bug.yml` triggers on issues opened-with-`bug`-label OR labeled-with-`bug`-after-the-fact. Posts a structured Slack message (header + title link + author/repo context) via incoming webhook to the #telemetry channel. Covers both: - Manual issue filings labeled `bug` - /bicameral-report-bug skill submissions (which file with labels `dev,bug` per skills/bicameral-report-bug/SKILL.md) Setup required (one-time, by repo admin): 1. Create an incoming webhook for #telemetry on Slack 2. Add the URL as repo secret SLACK_TELEMETRY_WEBHOOK_URL Without the secret the job is a no-op. The slack-github-action step fails closed when the webhook URL is empty, so no spurious notifications. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
release(prep): v0.14.1 — SBOM emitter fix + version bumps
feat(infra): post to Slack #telemetry when a bug ticket is filed
This was the wrong shape — committing internal-ops Slack routing into the public-ish repo. The cleaner pattern is GitHub's official Slack app (slack.github.com), which lives entirely in Slack workspace config: no code commit, no repo secret, can be toggled or filtered without opening a PR. Setup (one-time, by a Slack workspace admin in #telemetry): /github subscribe BicameralAI/bicameral-mcp issues +label:bug That single command covers: - Manual issue filings labeled `bug` - /bicameral-report-bug skill submissions (which file with `dev,bug`) - Late-labeled issues (re-fires when `bug` is added) Reverts PR #265 (commit c545d6b). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
revert: drop notify-slack-on-bug workflow (use GitHub Slack app instead)
Same divergent-histories pattern as the v0.14.0 PR #247 merge — took --allow-unrelated-histories with dev as source of truth for the four content conflicts: - .github/workflows/publish.yml: dev has the proper SBOM steps (with the new sbom_emit.py that installs the wheel into a temp venv). main has the if:false hotfix from #261/#262. Take dev — restoring SBOM gen for v0.14.1 is the whole point. - pyproject.toml: dev=0.14.1, main=0.14.0. Take dev. - RECOMMENDED_VERSION: dev=0.14.1, main=0.14.0. Take dev. - CHANGELOG.md: dev has v0.14.1 + v0.14.0 release sections (with the v0.13.7-9 entries on main not folded in). Take dev — release tags preserve the v0.13.x history. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (50)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Fast-follow on v0.14.0. Bundles the dev work merged since v0.14.0 cut + the SBOM emitter fix that was the actual blocker for v0.14.0's publish chain.
Closes
Highlights
release/sbom_emit.pyrewritten — installs the wheel into a temp venv, then runscyclonedx-py environment --output-file <out> <venv-python>. Fixes the v0.14.0 publish-pipeline halt (skipped by hotfixes hotfix(publish): skip SBOM gen + attest for v0.14.0 #261/hotfix(publish): drop SBOM artifacts from upload step (#261 followup) #262). v0.14.1's publish run will produce the SBOM artifacts that v0.14.0 lacked.bicameral-syncskill auto-binds ungrounded decisions (fix(skill): bicameral-sync stalls on ungrounded decisions, requiring manual bind + re-sync #263 P0). New step 1.5 between Sync HEAD and Resolve Compliance: locate symbol viavalidate_symbols, callbicameral.bind, concatenate the returnedPendingComplianceCheckwith the original list, proceed to step 2 in the same invocation. Closes the autonomy hole that broke/bicameral-syncon the first commit after a fresh ingest.bicameral-mcp diagnoseCLI (feat(cli): #252 Layer 3 — bicameral-mcp diagnose CLI #257, Layer 3 of link_commit fails with SurrealDB 'Invalid revision 116' + recommended v0.13.9 not on PyPI #252). Read-only diagnostic command for ledger health.bm25s>=0.3.8(build(deps): update bm25s requirement from >=0.2.0 to >=0.3.8 #259),sqlite-vec>=0.1.9(build(deps): update sqlite-vec requirement from >=0.1.0 to >=0.1.9 #260).Cleanup
bicameral-mcp-classifyconsole-script entry frompyproject.toml(pointed atcli.classify:main, deleted in chore(v1): partial scale-down — revert preflight HITL bypass + decision_level wiring from dev (sibling to #242) #244).Merge resolution
mainanddevstill have divergent histories (option 3 again from PR #247). Local merge with--allow-unrelated-histories; 4 content conflicts resolved by taking dev's version (the v0.14.1 source of truth):.github/workflows/publish.yml— dev's full SBOM steps (re-enabled now that sbom_emit is fixed) over main'sif: falsehotfixpyproject.toml— dev's 0.14.1 over main's 0.14.0; dev's full dependency list over main'sRECOMMENDED_VERSION— dev's 0.14.1 over main's 0.14.0CHANGELOG.md— dev's v0.14.1 + prior content over main's v0.13.7-9 entries (release tags preserve the v0.13.x history independently)Test plan
v0.14.1, recreate GitHub Release, fire publish workflow. Verify SBOM artifacts attach this time — that's the whole point of v0.14.1.🤖 Generated with Claude Code