Skip to content

fix: Sprint 0-3 — Security, ADRs, hygiene, DX (91 files, ~1200 lines net) - #132

Merged
danzeroum merged 13 commits into
mainfrom
claude/analyze-repo-technical-debt-6OvAD
May 19, 2026
Merged

danzeroum merged 13 commits into
mainfrom
claude/analyze-repo-technical-debt-6OvAD

Conversation

@danzeroum

@danzeroum danzeroum commented May 19, 2026

Copy link
Copy Markdown
Owner

Sprint 0 + Sprint 1 + Sprint 2 + Sprint 3

PR acumulada com 12 commits cobrindo todos os sprints da dívida técnica crítica identificada na auditoria de 20/05/2026.


Sprint 0 — Emergência de Segurança

  • S-02: verify_signature() implementado com HMAC real + hmac.compare_digest(). EthicalVerdict ganhou blake3_hash binding evidência↔verdict. Forjado BLOCK→ALLOW rejeitado.
  • S-03: apagar_teste_slm.yaml deletado do diretório de producão.
  • S-09: HMAC singletons (GoalDriftSentinel, DelegationLedger, EthicalContextEngine) com chave resolvida em tempo de assinatura via Callable[[], bytes].
  • S0-05: CORS allow_origins=["*"]BTV_CORS_ORIGINS env var.
  • ADR-063 P2: const_assert_eq!(size_of::<TechnicalEvidence>(), 9632) ativo. from_bytes() valida version [1–3].
  • appeals.db: coluna hash_algorithm TEXT DEFAULT 'blake3' adicionada.

Sprint 1 — ADRs Arquiteturais (todos mergeados na PR #131)

  • ADR-060: BiasDeclaration::new() retorna Result
  • ADR-061: Decision::Block(BlockReason) com semântica distinta de Deny
  • ADR-062: AppealRecord fixed-size + verify_appeal_text() constant-time
  • ADR-063: const assert ativo (Phase 2 nesta PR)
  • ADR-064: PolicyWatcher com ed25519-dalek

Sprint 2 — Higiene Crítica

  • H-01: 6 pares ADR renumerados 0065–0070 via git mv. Índice reconstruido: 46 → 70 ADRs.
  • H-02: Versão única 0.1.0-alpha.1 em todos os manifests (era 7 versões diferentes).
  • H-04: cargo fmt --check adicionado ao CI como Step 0.
  • H-05: YAML schema discriminator (policy-rules / profile / agent-guard). default.yaml agora corretamente avaliado.
  • H-06: AbliterationDetector v1.1.0 substituído por import da v1.2.0. EthicalContextEngine removida de __all__.
  • H-07: 720h (GDPR Art. 22, 30 dias) vs 24h (BTV SLA técnico) documentados explicitamente em quickstart.md.
  • H-08: Licenças de 5 crates Rust padronizadas com license.workspace = true.
  • H-10: except silenciosos em policy_engine.py e governance_gateway.py agora emitem logger.warning com filename + detalhe.

Sprint 3 — Qualidade & DX

  • Q-01: decide() refatorado de 544 linhas para 8 funções focadas + coordenador ~40 linhas. Acumuladores _AdjSignals, _SLMMeta, _ComplianceMeta fluem pelo pipeline. Zero mudanças de comportamento.
  • Q-02: Porta gateway unificada para :8080 em README.md, quickstart.md e 2 docs de integração.
  • Q-03: REPORT e REFUSE adicionados ao enum VerdictAction no OpenAPI spec (estavam implementados no Python mas ausentes do contrato).
  • Q-04: Reserva GPU NVIDIA removida do docker-compose.yml padrão. Criado ops/docker-compose.gpu.yml como opt-in explícito.

Checklist

  • cargo build -p buildtovalue-kernel — clean
  • const_assert_eq!(size_of::<TechnicalEvidence>(), 9632) compilando
  • verify_signature() HMAC real — forjado BLOCK→ALLOW rejeitado
  • Nenhum ADR duplicado; índice cobre 0001–0070
  • Versão única 0.1.0-alpha.1
  • cargo fmt --check no CI
  • decide() ≤50 linhas por função
  • Porta 8080 consistente
  • Docker Compose sem dependência GPU obrigatória
  • Syntax check Python limpo

Pendente fora de PR

S-01 — Purga do histórico Git (credenciais rastreadas): requer git push --force após BFG/filter-repo. Janela de manutenção separada.

Sprint 4 na fila (Marketing Honesto)

  • M-01: Professional tier → Beta + remover SLA sem runbooks
  • M-02: README dual-track (Engenheiros / DPOs)
  • M-03: SDK Python no PyPI
  • M-04: One-path quickstart com aha moment (Docker 3 comandos)
  • M-05: Claims sem fonte removidos/citados
  • M-06: benchmarks/REPRODUCE.md

claude added 9 commits May 19, 2026 15:03
Two Rust hygiene fixes in gatekeeper.rs identified during the PR #130
audit (briefing Alertas 2 and 3):

1. p99_latency_ms was a running maximum, not a percentile (PR-5)

   The previous `if latency > p99 { p99 = latency }` pattern is
   max(), not P99 — any single spike permanently raises the reported
   value, making the <50ms SLA assertion meaningless.

   Fix: 1 000-sample circular ring buffer (Box<[f32; 1000]>) on the
   Gatekeeper struct. update_metrics() writes into the ring O(1);
   get_metrics() sorts a copy on demand (called only by metrics
   scrapers, not per-scan). No external dependencies added.

   GatekeeperMetrics gains p50/p95/p99/p999 fields; the Python FFI
   dict (bridge/mod.rs) is updated to expose all four. `let mut` is
   required on the MutexGuard<Gatekeeper> in get_gatekeeper_metrics
   because get_metrics() now takes &mut self.

2. unwrap_or_else(|_| panic!(...)) → .expect() (Alerta 3)

   gatekeeper.rs:154 used `unwrap_or_else(|_| panic!(...))`, which is
   the anti-pattern forbidden by BTV invariants. Replaced with
   `.expect("blake3_hash is [u8;32] — slice [0..8] is infallible")`.

Verified: `cargo build -p buildtovalue-kernel` clean (0 errors, 0 new
warnings); `cargo test -p buildtovalue-kernel` 12/12 pass including
test_gatekeeper_aggregates_worst_case.

https://claude.ai/code/session_01T3z96dywN5mpiAyCyEe5KP
…olicy reload

ADR-060 — BiasDeclaration validated constructor:
  - BiasDeclaration::new() now returns Result<Self, BiasDeclarationError>
    (Err on calibration_date==0 or test_dataset_size==0).
  - impl Default gated with #[cfg(test)] — production code that calls
    ::default() will not compile.
  - BiasDeclaration::aggregate() added for gatekeeper worst-case path where
    calibration_date may legitimately be 0 (logged as warning via
    is_calibration_valid()).
  - ~30 static bias_declaration() impl sites updated with .expect("static
    bias values are valid"). policy.rs::bias_declaration fixed: dataset_size
    0 → 1000. evidence/technical.rs and validators/mod.rs use aggregate().
  - BiasDeclarationError added to core/errors.rs.

ADR-061 — Decision::Block + NegotiationDeadlockReason:
  - Decision enum (Allow/Log/Deny/Block/Redact/Report) in core/types.rs.
    Deny = policy rejection (24h SLA); Block = active threat (Trust Score
    penalty). Distinct semantics prevent Ledger misclassification.
  - NegotiationDeadlockReason enum (4 variants, repr(u8)).
  - DeadlockResolutionError struct: explanation field required by constructor
    (empty explanation → Err); uses serde_array_compat::size_256.

ADR-062 — AppealRecord off-chain verification:
  - AppealRecord fixed-size struct in core/types.rs (verdict_id, hashes,
    timestamps). Zero heap allocation — safe for hot-path serialisation.
  - verify_appeal_text() in explanation_store.py: blake3(text) compared to
    Ledger hash via hmac.compare_digest() (constant-time). Falls back to
    sha3-256 if blake3 package is absent.

ADR-063 — Compile-time size invariants:
  - const _: () = assert!(size_of::<BiasDeclaration>() == 512, ...) active.
  - TechnicalEvidence assert deferred until Vec<u8> fields become fixed-size
    arrays (comment documents Phase 2 path).

ADR-064 — PolicyWatcher with Ed25519:
  - policy/loader.rs: PolicyWatcher holds VerifyingKey (public only).
    verify_and_load() verifies Ed25519 signature before parsing YAML.
    Parsing never occurs on invalid signature (PolicyLoadError::InvalidSignature).
  - ed25519-dalek = { workspace = true } added to kernel/Cargo.toml.
  - Python policy_loader.py (pre-existing) implements the same guarantee
    via cryptography.hazmat.

Build: cargo build -p buildtovalue-kernel — clean, 0 errors.
Tests: cargo test -p buildtovalue-kernel — 302 unit + 43 integration = 0 failures.

https://claude.ai/code/session_01T3z96dywN5mpiAyCyEe5KP
…ash_algorithm param

Ressalva 1 from ADR-062 validation review:

The previous implementation silently fell back to sha3-256 when the blake3
package was absent. This created a data integrity hazard: records written
with blake3 would fail verification in environments without blake3, and
records written with sha3-256 (the fallback) would fail in environments
with blake3 — all without any error, just False returned from compare_digest.

Fix:
- Add hash_algorithm: str = 'blake3' parameter. Callers must pass the
  algorithm used when the hash was recorded (stored alongside the record).
- If hash_algorithm='blake3' and the package is absent: raise RuntimeError
  with an explicit message. Falling back silently is prohibited.
- If hash_algorithm is unknown: raise ValueError immediately.
- sha3-256 remains supported as a named algorithm (not a fallback).
- _SUPPORTED_ALGORITHMS frozenset documents the contract at module level.

The Ledger (Rust side) always uses BLAKE3; the default 'blake3' is correct
for production. sha3-256 is retained for environments that explicitly opted
in (test fixtures, migration tooling) where the algorithm is stored and
passed explicitly.

https://claude.ai/code/session_01T3z96dywN5mpiAyCyEe5KP
…P rotation

Three singletons in the lifespan were initialized with get_hmac_key() at
startup, capturing a snapshot of the key bytes. After rotate_hmac_key()
(SIGHUP), those singletons continued signing with the stale key — the gap
that PR-4 documented as "partial" in the "PR-4 partial" comments.

GoalDriftSentinel:
  - __init__ gains hmac_secret_fn: Optional[Callable[[], bytes]] = None.
  - hmac_secret: bytes → Optional[bytes] = None for backwards compat.
  - Validates: exactly one of (hmac_secret, hmac_secret_fn) must be provided.
  - sign_drift() calls self._secret_fn() at each invocation — key read is
    deferred to signing time, so rotate_hmac_key() propagates immediately.

DelegationLedger:
  - __init__ gains hmac_key_fn: Optional[Callable[[], bytes]] = None.
  - hmac_key: Optional[bytes] = None (was required-but-optional-typed).
  - Both signing call sites (delegate() and verify_contract()) use
    self._key_fn() instead of self._secret.

EthicalContextEngine:
  - The stale signing_key=get_hmac_key() kwarg in app.py is removed.
    EthicalContextEngine has never accepted signing_key in its __init__
    (the kwarg was a stale remnant predating the current __init__ signature).
    ECE delegates signing to PolicySigner, not raw HMAC. The call would
    have raised TypeError at lifespan startup.

app.py:
  - EthicalContextEngine(): no HMAC arg (correct — ECE uses PolicySigner).
  - GoalDriftSentinel(hmac_secret_fn=get_hmac_key): callable passed.
  - DelegationLedger(hmac_key_fn=get_hmac_key): callable passed.
  All three "PR-4 partial" comments removed — S-09 is fully closed.

Gate: ADR-060..064 were merged on this branch as commit 8419caa.
This commit uses the now-unblocked Python feature path (Trilho C).

https://claude.ai/code/session_01T3z96dywN5mpiAyCyEe5KP
Workspace Integrity (clippy -D expect_used):
  BiasDeclaration::from_static() added to types.rs with #[allow(clippy::expect_used)].
  30 bias_declaration() implementations replaced BiasDeclaration::new(...).expect("static
  bias values are valid") with the new from_static() constructor — static compile-time
  constants that cannot fail don't need runtime error propagation.

  gatekeeper.rs:163: try_into().expect() on blake3_hash slice replaced with
  copy_from_slice() — no .expect() call, genuinely infallible.

  gatekeeper.rs:351: iter().copied().collect() → .to_vec() (clippy::iter_collect_into).

E2E governance startup (TypeError):
  context_engine.EthicalContextEngine.__init__ required signing_key: bytes.
  Commit 7299aee removed the arg from app.py (correct intent for S-09) but did not
  first make signing_key optional and add signing_key_fn. Service crashed at lifespan.

  context_engine.py: same callable-pattern applied as GoalDriftSentinel and
  DelegationLedger — signing_key_fn: Optional[Callable[[], bytes]] accepted,
  key resolved at verdict-signing time via self._signing_key_fn().

  app.py: EthicalContextEngine(signing_key_fn=get_hmac_key) — S-09 fully closed
  for all three verdict-signing singletons.

https://claude.ai/code/session_01T3z96dywN5mpiAyCyEe5KP
S-02 (most tragic finding — fixable before any CISO demo):
  context_engine.EthicalContextEngine.verify_signature() was a 3-line stub
  that returned `len(verdict.hmac_signature) == 64`. Any 64-char hex string
  passed verification — including verdicts where final_action was forged
  BLOCK→ALLOW after signing. The docstring admitted "simplified — full impl
  needs stored hash".

  Fix has two parts:
  - context_engine_types.EthicalVerdict gains blake3_hash: str field, the
    audit-evidence binding required to recompute the signed payload. Without
    it the verdict was structurally unverifiable.
  - context_engine.decide() populates blake3_hash=evidence.blake3_hash at
    sign time. The verdict is now self-verifying from the ledger record +
    current HMAC key (no separate evidence lookup needed).
  - verify_signature() recomputes f"{verdict_id}|{blake3_hash}|{final_action}
    |{timestamp}" and compares with hmac.compare_digest() (constant time).

  Verified roundtrip locally:
    valid verdict        → True
    BLOCK→ALLOW forgery  → False  (the headline tamper case)
    arbitrary "a"*64     → False  (closes the no-op bug literally)
    swapped blake3_hash  → False

S-03:
  data/policies/apagar_teste_slm.yaml ("delete-test_slm.yaml") was a
  forgotten test file loaded by PolicyEngine._load_policies() via
  rglob("*.yaml") in production. Deleted; no callers — file contained
  only `slm: { enabled: false }`.

Ripple audit: EthicalVerdict has exactly one production construction site
(context_engine.decide). A second class named EthicalVerdict exists in
contestability_loop.py with a different schema (decision/bias_declaration
fields) — name shadowing technical debt tracked separately, unaffected
by this change.

https://claude.ai/code/session_01T3z96dywN5mpiAyCyEe5KP
ADR-063 Phase 2 (TechnicalEvidence size invariant):
- Activate compile-time assert in core/types.rs — confirmed 9632 bytes.
  The assert was deferred pending Vec<u8> field removal; struct was already
  fully fixed-size ([u8; 7072] _reserved_metadata), so activation is safe.
- Harden from_bytes(): add post-read version check (must be 1–3) to reject
  zero-filled or truncated buffers before they propagate as live evidence.
  Adds safety comment explaining why the unsafe block meets its preconditions.
- Update ADR-063 status to Active; document phase 2 completion.

appeals.db schema (ADR-047 follow-up):
- Add hash_algorithm TEXT DEFAULT 'blake3' to CREATE TABLE and the safe
  ALTER TABLE migration block. Existing rows default to 'blake3'.
  Aligns appeals audit records with the BLAKE3 evidence-binding introduced
  by S-02 (verify_signature / blake3_hash in EthicalVerdict).

CORS (S0-05): already resolved in a prior commit; no change needed.

https://claude.ai/code/session_01T3z96dywN5mpiAyCyEe5KP
…iminator

H-01 — ADR cleanup (6 duplicate pairs, 3 orphans):
- Renumber 6 "loser" duplicates to 0065–0070 (preserving git history via
  git mv). Canonical ADRs at their original numbers remain unchanged.
  Updated internal ADR number in each renamed file header.
- Archive 3 orphans to docs/adr/archive/: ADR-043-grant-decision-adapter.md,
  TBD-grant-decision-adapter.md (both superseded by 0057), ADR-051.md
  (duplicate of 0051). Archive README documents supersession chain.
- Update 0000-adr-index.md: remove reservation note for 0043–0048/0050;
  update Group L (ADR-0060 SaaS → now ADR-0070); add Group M listing
  ADR-0043 through ADR-0070; update statistics (46 → 70 total ADRs).
- Fix ci.yml: paths and W2.9 step now reference canonical
  0057-grant-decision-adapter.md instead of archived ADR-043 file.

H-02 — Version unification to 0.1.0-alpha.1 / 0.1.0a1:
- Rust workspace Cargo.toml: 1.0.0 → 0.1.0-alpha.1 (all inheriting crates
  auto-updated via workspace inheritance).
- btv-core, buildtovalue, btv-governance: 3.0.0-alpha.1 / 1.0.0 → 0.1.0-alpha.1.
- Fixed btv-core version constraint in buildtovalue facade Cargo.toml.
- Python: pyproject.toml, __init__.py (x2), app.py, cli/main.py → 0.1.0a1.
- Dashboard sidebar, Makefile, gateway/src/main.rs, spec/openapi.yaml updated.

H-04 — CI: add cargo fmt --check to crate_release_audit.yml (step 0, before
  all other steps). rustfmt component already present in dtolnay/rust-toolchain.

H-05 — YAML schema discriminator (prevent PolicyEngine cross-contamination):
- Add schema_type to PolicyEngine._KNOWN_TOP_LEVEL_KEYS.
- Add _POLICY_RULES_SCHEMA_TYPES / _NON_POLICY_SCHEMA_TYPES class-level sets.
- In _parse_policy_file: skip files where schema_type ∈ {profile, agent-guard}
  with a DEBUG log (not WARNING — skipping is expected behaviour, not a mistake).
- Add schema_type: "policy-rules" to governance_v1.yaml and default.yaml.
- Add schema_type: "profile" to base.yaml.
- Add schema_type: "agent-guard" to chatbot-vendor-*.yaml and chatbot-rag-external.yaml.

https://claude.ai/code/session_01T3z96dywN5mpiAyCyEe5KP
@danzeroum danzeroum changed the title fix: Sprint 0 residual — ADR-063 Phase 2 + appeals.db schema + CORS confirmation fix: Sprint 0 residual + Sprint 2 — ADR-063 P2, appeals.db, ADR dedup, version 0.1.0-alpha.1, CI fmt, YAML discriminator May 19, 2026
claude added 3 commits May 19, 2026 19:26
…H-10 logging

H-06 — Deduplicate AbliterationDetector and EthicalContextEngine:
- model_integrity_verifier.py: delete v1.1.0 AbliterationDetector class (~90 lines)
  and import the production v1.2.0 from abliteration_detector.py (ADR-051 Fase 2).
  IntegrityVerifier now uses the implementation with timeout enforcement,
  extended probe catalog, confidence scoring, and explain_decision().
- context_engine.py: remove "EthicalContextEngine" from __all__. The legacy v1.9.1
  judiciary class stays (governance_gateway.py depends on it) but is no longer
  re-exported as the public API. Canonical export: governance/__init__.py →
  ethical_context_engine.EthicalContextEngine. Added module-level deprecation note.

H-07 — Reconcile 720h vs 24h in appeal documentation:
- quickstart.md: annotate all three 720h references to clarify the distinction:
  720h = GDPR Art. 22 regulatory appeal window (30 days, set by regulation);
  24h = BTV internal SLA for human review (set by BTV SRE). Both are correct
  and refer to different obligations. Reference added to docs/compliance.md.

H-08 — Crate license declarations:
- Add license.workspace = true to btv-executive, btv-judicial, btv-redaction,
  btv-sigma, btv-types. All five were missing a license field; they now inherit
  "MIT OR Apache-2.0" from the workspace package table.

H-10 — Logging on critical silent except blocks:
- policy_engine.py._load_policies: bare `except Exception: pass` → logs
  WARNING with file name and exception for every malformed YAML skipped.
- policy_engine.py._parse_policy_file: bare `except (KeyError, ValueError): continue`
  → logs WARNING with file name, rule_id, and exception for every bad rule.
- governance_gateway.py: add `import logging` + `logger`, then replace bare
  `except Exception: pass` on refusal-record persistence with WARNING log
  (fail-open semantics preserved — refusal verdict still proceeds).

https://claude.ai/code/session_01T3z96dywN5mpiAyCyEe5KP
Todos os exemplos de docs e integração apontavam para :3000 (porta legada).
Gateway expõe :8080 em todos os ambientes. Corrigir para consistência e
evitar onboarding quebrado.

Arquivos: README.md, docs/quickstart.md,
docs/integrations/chatbot-{internal,external}-llm.md

https://claude.ai/code/session_01T3z96dywN5mpiAyCyEe5KP
Q-01: extrair decide() (544 linhas) em 8 helpers + coordenador ~40 linhas.
  _AdjSignals/_SLMMeta/_ComplianceMeta como acumuladores mutáveis;
  _decide_hard_block, _decide_run_guards, _decide_accumulate_signals,
  _decide_slm, _decide_adjust_risk, _decide_ethical_verdict,
  _decide_compliance, _decide_output_pipeline, _decide_persist_trust.
  Comportamento idêntico — refatoração pura sem mudança de lógica.

Q-03: adicionar REPORT e REFUSE ao enum VerdictAction do OpenAPI.
  REPORT (ADR-043: composite_risk >= threshold com findings) e
  REFUSE (RefusalGate MOSAIC-inspired) estavam ausentes da especificação.

Q-04: tornar GPU opcional no Docker Compose.
  Remover reservations.devices do docker-compose.yml (falha em hosts sem GPU);
  criar docker-compose.gpu.yml como override explícito para workloads GPU.

https://claude.ai/code/session_01T3z96dywN5mpiAyCyEe5KP
@danzeroum danzeroum changed the title fix: Sprint 0 residual + Sprint 2 — ADR-063 P2, appeals.db, ADR dedup, version 0.1.0-alpha.1, CI fmt, YAML discriminator fix: Sprint 0-3 — Security, ADRs, hygiene, DX (91 files, ~1200 lines net) May 19, 2026
@danzeroum
danzeroum merged commit 6dcb9bb into main May 19, 2026
15 of 21 checks passed
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.

2 participants