Skip to content

chore(release): integrate v1.0 PRs + bump 1.0.0 + CHANGELOG#45

Merged
ernestprovo23 merged 11 commits into
mainfrom
chore/v1-integrate-release-prep
Jun 14, 2026
Merged

chore(release): integrate v1.0 PRs + bump 1.0.0 + CHANGELOG#45
ernestprovo23 merged 11 commits into
mainfrom
chore/v1-integrate-release-prep

Conversation

@ernestprovo23

Copy link
Copy Markdown
Member

v1.0.0 release integration

Integrates the three v1.0 readiness PRs into a single release-prep branch, bumps to 1.0.0, and adds a CHANGELOG.md. This PR supersedes #42, #43, and #44 — all of their work is merged here.

What merged

Conflict resolutions (4)

Version bump

  • pyproject.toml [project].version1.0.0 (distribution name stays conclave-cli).
  • src/conclave/__init__.py __version__1.0.0.
  • Historical version-history rows left untouched.

Build + tests (local, isolated worktree venv, pytest 9.1.0)

  • ruff check . clean · ruff format --check . clean (31 files).
  • 235 passed, 0 failed (union of distribution + keyleak [23] + synthesizer [21]).
  • Coverage 90.03% (floor fail_under=75).
  • python -m buildconclave_cli-1.0.0-py3-none-any.whl + conclave_cli-1.0.0.tar.gz.

CHANGELOG

New CHANGELOG.md (Keep-a-Changelog) with a [1.0.0] - 2026-06-14 entry covering distribution, key-leak hardening, synthesizer versioning, and release engineering, plus a post-1.0 roadmap note (vote mode #3, stdio MCP server #8) and compare links.

🤖 Generated with Claude Code

…ep audit

Distribution + release-engineering for the v1.0 cut (readiness review must-dos
1, 2 scaffolding, and 6):

- pyproject: [project].name -> conclave-cli (PyPI `conclave` is an unrelated
  project). Command (`conclave`), import package (`conclave`), and repo are
  unchanged. Adds [project.urls]. Version left at 0.3.0 (the 1.0.0 bump is the
  release commit per RELEASING.md).
- README: install is now `pip install conclave-cli`, with a note on the
  install-name vs command/import split; editable source install kept for dev.
- .github/workflows/release.yml: OIDC Trusted-Publishing publish workflow,
  triggered on `release: published`. build (python -m build) -> pypi-publish
  (pypa/gh-action-pypi-publish, no token, PEP 740 attestations) -> sign
  (Sigstore keyless, attaches .sigstore bundles to the Release). Every `uses:`
  pinned to a full commit SHA with a `# vX.Y.Z` comment. Inert until a Release
  is published AND the conclave-cli Trusted Publisher is configured.
- test.yml: new fail-closed `pip-audit` job auditing the resolved deps.
- requirements-dev.lock: hash-pinned dev + runtime tree (uv pip compile
  --generate-hashes) for reproducible installs.
- RELEASING.md: operator runbook (one-time Trusted-Publisher setup, cut-a-release
  checklist, post-release verification, rollback/yank).
- DOCUMENTATION_INDEX.md: index the new release-eng artifacts + version history.
#5)

The synthesizer/judge path is the heart of conclave's "council" value prop but
was undocumented and lightly tested, risking silent degradation. This makes it
sound and observable for 1.0.

Investigation (current behavior, unchanged):
- synthesizer = constructor arg, else config `synthesizer:`, else built-in
  default "claude" (registry.DEFAULT_SYNTHESIZER). Same model judges in
  adversarial and consolidates in debate.
- degraded paths were ALREADY observable, not silent: no-usable-answers,
  unkeyed synthesizer, and synthesizer-call-failure each set
  CouncilResult.synthesis_error (adversarial: AdversarialResult.verdict_error,
  mirrored). synthesis stays None; member answers preserved. No silent
  quiet-concat path existed to fix -- confirmed + pinned with tests.

Changes:
- version the synthesis prompt set: new conclave.prompts.SYNTHESIS_PROMPT_VERSION,
  re-exported from council, stamped onto every CouncilResult as `prompt_version`
  (lazy default_factory avoids the prompts<->models import cycle). Prompt text is
  byte-stable; the constant + text are pinned so a prompt change without a version
  bump fails CI.
- document selection/default/configurability/fallback in the council module
  docstring + _synthesize docstring, and a README "Synthesizer behavior" section.
- DOCUMENTATION_INDEX: new test file row, CouncilResult field note, changelog row.
- tests/test_synthesizer.py (21 tests): default + arg/config/CLI override
  selection; observable degradation for synthesize, debate, and adversarial judge
  (unkeyed + call-failure); prompt-version stability across every mode.

No non-synthesis behavior changed; happy-path synthesis output is byte-for-byte
unchanged. Mocks at the existing httpx-transport boundary (offline).
…andlers

pytest 9.x's logging plugin attaches LogCaptureHandler instances to every
logger during a test, inflating raw len(logger.handlers). The two handler-count
assertions in test_logging.py asserted an exact total of 1 and now see 3 under
pytest 9.1.0, failing in CI (and on a fresh local resolve) regardless of source
changes. Filter capture handlers out so the tests assert on the single handler
get_logger actually installs -- preserving their intent while being robust to
the runner. Production logging code is unchanged (outside pytest it adds exactly
one StreamHandler).
… capture

CI on this branch resolved pytest 9.1.0 (deps are pinned >=8.0.0; main's last
green run predates the 9.1.0 release). pytest 9.x attaches its LogCaptureHandler
(a StreamHandler subclass) directly to the non-propagating `conclave` logger
during a run, so `len(logger.handlers) == 1` now sees 3 handlers and
test_logging.py's one-shot-configuration assertions fail across 3.11/3.12/3.13.

Count only conclave's own handler via `type(h) is logging.StreamHandler`
(pytest's is a subclass) instead of all handlers. This preserves the test's
intent exactly -- the factory installs one StreamHandler and never duplicates it
-- while ignoring pytest-injected capture handlers, and is stable across pytest
versions. No production code changed.
Audit and harden the BYO-keys leak surface ahead of v1.0, with regression
tests per vector (tests/test_keyleak_audit.py):

- cache write path: assert a planted key-shaped secret echoed in a provider
  error never reaches a cache file/filename/key (cache stores the already-
  redacted CouncilResult); add an explicit invariant comment in cache.store.
- streaming path: assert a mid-stream provider error echoing a key is absent
  from every streamed StreamEvent AND the final ModelAnswer; document the
  transport->providers redaction boundary in stream_sse.
- __repr__/__str__: assert no adapter/config/result object renders key
  material (no object stores a key; transient request headers are not
  retained).
- provider 400/422 echo: assert buffered error capture runs through redact()
  for prefixed and unprefixed custom-endpoint keys.
- httpx/httpcore DEBUG logging (out-of-band of redact): document loudly and
  add an opt-in guard_transport_logging() helper that drops transport DEBUG
  records (the only level that emits auth headers).
- audit-found gap (not in the original attack map): the partial-failure
  catch-alls in Council.fan_out and streaming._drive_member built error
  strings from raw exception text without redact(); wrap both in redact().

Add a Threat model section to SECURITY.md (trust boundary, what IS protected,
accepted limitations, vector map) without touching the disclosure policy. Add
.gitleaks.toml allowlisting the obviously-fake test fixtures (test tree only).

Tests 191 -> 209; coverage 89%+. Disclosure policy unchanged.
…ture

The two handler-count assertions in test_logging.py counted ALL handlers on
the conclave logger, which fails under newer pytest (9.x) whose logging plugin
injects LogCaptureHandler instances. Filter to conclave's own StreamHandler via
a _app_stream_handlers helper (excludes any handler whose class name contains
"Capture") so the tests assert on conclave's one-shot config without coupling to
the pytest version. Passes under pytest 8.4.2 and 9.1.0. Test-only; no source
change. Unblocks CI for the key-leak audit PR.
…hain (RANK 1/5)

The httpx exception raised on a transport failure carries a live .request
whose .headers hold the Authorization/x-api-key value. Surfacing it as
TransportError.__cause__/__context__ left the key one cause-chain hop away,
leaking under traceback.format_exception, logging.exception, or a cause-chain
repr. The four raise sites in post_json/stream_sse now route through
_raise_transport_error (raise ... from None) plus a boundary clear that nulls
__context__, so neither a formatter nor a direct attribute walk can reach the
header-bearing exception. Pinned by V8 tests.
…t-out (RANK 6)

Council.__init__ now calls transport.guard_transport_logging() automatically
unless allow_transport_debug_logging=True, so a process holding a real key is
protected from httpx/httpcore DEBUG header leakage out of the box. The guard is
idempotent and scoped to the httpx/httpcore loggers only; it never touches the
host root logger. SECURITY.md reworded from opt-in to default-on/opt-out, with a
new What-IS-protected bullet and vector-map rows for the cause-chain hardening
(RANK 1/5) and the default-on guard (RANK 6). Adds V9 (guard default-on +
opt-out) and V10 (client close hygiene, RANK 8) regression tests.
…' into chore/v1-integrate-release-prep

# Conflicts:
#	tests/test_logging.py
…to chore/v1-integrate-release-prep

# Conflicts:
#	DOCUMENTATION_INDEX.md
- pyproject [project].version 0.3.0 -> 1.0.0 (name stays conclave-cli)
- src/conclave/__init__.py __version__ -> 1.0.0
- DOCUMENTATION_INDEX current version line -> 1.0.0; index rows for CHANGELOG + keyleak-audit tests
- new CHANGELOG.md (Keep-a-Changelog) covering v0.3.0 -> v1.0.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant