Repository navigation
feat(llc): board approval gates — GH#8214 - #8458
Conversation
The previous commit put create_llc_work_items at 022 and llc_goals at 024,
but create_llc_work_items has sa.ForeignKey("llc_goals.id") — PostgreSQL
requires the referenced table to exist at CREATE TABLE time, so that order
would fail with "relation llc_goals does not exist".
Correct linear chain (respects FK dependencies):
021 (heartbeat_wakeup) — existing anchor
022 (llc_goals) — no FK deps, must come first
023 (organization_llc_extensions) — no FK deps on LLC tables
024 (create_llc_work_items) — FK to llc_goals.id, must come last
Head is still 024; downstream PRs (#8457, #8458, #8459) still need
down_revision = "20260523_024".
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Code Review — CHANGES REQUIRED7 confirmed bugs found across security, correctness, and deployment categories. Do not merge until items 1–2 are resolved. CRITICAL — deployment blockers1. Duplicate Alembic revision ID revision: str = "20260523_023"
Fix: Use 2. Stale down_revision: Union[str, None] = "20260523_022"Current chain on Dev_new_gui is Fix: HIGH — security + correctness3. The wrapper validates Fix: The decorator must compare 4. Two concurrent calls both SELECT the same PENDING row, both pass the guard, last writer wins. Two contradictory Redis events fire for the same Fix: Add 5. Redis publish fires before DB commit Both Fix: Move MEDIUM — HTTP 500 on invalid client input6.
Fix: Type the Pydantic fields as 7.
Fix: Same as #6. Note: The |
MVA-871 fixes applied ✅All 7 bugs fixed across 2 commits on this branch: Commit d334c62 — 6 bugs
Commit 4b5c2a8 — test fixes
|
Re-review — 6/7 fixes confirmed, 1 merge-order constraintAll 7 originally reported bugs have been fixed:
Merge-order constraint: This PR's migration has PR #8457 must be merged first. Once #8457 is on Dev_new_gui, this PR can be squash-merged cleanly. |
- Add ApprovalType enum (hire/strategy/budget_override/sprint_close) to enums.py
- LLCApproval SQLAlchemy model (llc_approvals table)
- Alembic migration 20260523_023: approvaltype + approvalstatus enums + table
- ApprovalService: request_approval, decide, get_pending + Redis pub/sub events
- requires_approval(gate_type) decorator for gating async methods
- API routes POST/GET /llc/approvals, POST /llc/approvals/{id}/decide
- 17 unit tests (all passing) covering service layer and decorator
Co-Authored-By: Paperclip <noreply@paperclip.ing>
- Rename migration 20260523_023→026, down_revision 022→025 (fits after llc_agent_budgets which now occupies 025) - requires_approval decorator: verify approval.company_id matches operation's target company_id to block cross-tenant auth bypass - decide(): add .with_for_update() to prevent concurrent-decide race - ApprovalRequest/ApprovalDecision: type company_id, requested_by_agent_id, decided_by_agent_id as uuid.UUID so Pydantic returns 422 on invalid input - Move _publish calls out of session.begin() — publish_requested/ publish_decided are now called by the routes after the transaction commits Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both 026 (activity log) and 027 (approvals) had down_revision=025, which would create two Alembic heads on merge. Activity log (026) merges first, so approvals (027) chains off it: 025→026(activity_log)→027(approvals). Co-Authored-By: Paperclip <noreply@paperclip.ing>
|
| Metric | Count |
|---|---|
| Total Violations | 7 |
| SSOT Violations (high priority) | 7 |
| Other Violations | 0 |
⚠️ 7 values have SSOT config equivalents!
These should be replaced with SSOT config imports:
Python:
from src.config.ssot_config import config
# Use: config.vm.main, config.port.backend, config.backend_urlTypeScript:
import config from '@/config/ssot-config'
// Use: config.vm.main, config.port.backend, config.backendUrl📖 See SSOT_CONFIG_GUIDE.md for documentation.
…new_gui 028 (approvals, PR #8458) merged into Dev_new_gui after this branch was cut. Secrets was using revision=027/down=026 which collides with approvals. Linear chain: 025→026(activity_log)→027(approvals)→028(secrets). Co-Authored-By: Paperclip <noreply@paperclip.ing>
…17) (#8467) * feat(llc): company-scoped secrets — versioned, encrypted, access control (GH#8217) - LLCSecret SQLAlchemy model: id, company_id, name (unique per co.), encrypted value (LargeBinary), version (auto-increments on update), created_by_agent_id, created_at, updated_at, revoked_at (soft-delete). - HKDF-SHA256 key derivation: per-company Fernet key from LLC_SECRET_MASTER_KEY env var; cross-company decryption is cryptographically impossible. - Alembic migration 20260523_027 (down_revision=20260523_026). - SecretService extending LLCServiceBase: set/get/revoke/list; SecretNotFound raised for missing or revoked secrets; activity_log wired when injected. - API routes /llc/secrets/{company_id}: list (no values), POST set, GET /{name} (decrypted value on explicit request only), DELETE /{name} revoke. X-Agent-Company-Id header enforces per-company access control. - 11 unit tests covering encrypt/decrypt round-trips, version increment, revoked-secret reactivation, cross-company isolation, missing-key error. - Wired into llc.api and llc.models __init__.py. Co-Authored-By: Paperclip <noreply@paperclip.ing> * fix(migrations): rename secrets migration to 028/down=027, merge Dev_new_gui 028 (approvals, PR #8458) merged into Dev_new_gui after this branch was cut. Secrets was using revision=027/down=026 which collides with approvals. Linear chain: 025→026(activity_log)→027(approvals)→028(secrets). Co-Authored-By: Paperclip <noreply@paperclip.ing> --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
Summary
ApprovalTypeenum (hire/strategy/budget_override/sprint_close) added tollc/models/enums.pyLLCApprovalSQLAlchemy model inllc/models/approval.pywith all required fields20260523_023creatingapprovaltype+approvalstatusPG enums andllc_approvalstable with compound indexesApprovalServiceinllc/services/approval.pywithrequest_approval,decide,get_pending, and best-effort Redis pub/sub events onllc:approval_requested/llc:approval_decidedrequires_approval(gate_type)decorator for gating async service methods; attaches__approval_gate__metadata for introspectionPOST /llc/approvals,GET /llc/approvals?company_id=...,POST /llc/approvals/{id}/decidellc/api/__init__.pyupdated to include the new approvals routerTest plan
llc/tests/test_approvals.py— all passing (pytest 17 passed in 0.28s)requires_approvaldecorator: valid approval passes, missing ID raises, wrong type raises, non-approved status raises, metadata attachmentpython3 -m py_compileon all 5 new/changed files20260523_023with properdown_revision = "20260523_022"andcheckfirst=Trueenum creationCloses GH#8214
🤖 Generated with Claude Code