# CBB-Sign — Level of Effort Estimate

**Date:** 2026-06-12
**Status:** DRAFT — pending review
**Revised:** 2026-06-12 — reconciled with the cbb-sign SPEC (see Revision History). Scope grew from the original 97.5h estimate; see Summary.

> **Authority note:** Where this LOE and the cbb-sign SPEC (`CBB_Sign/04_-_Highly_Confidential/SPEC/`) differ, the **SPEC is correct**. This LOE is an effort estimate derived from the SPEC, not an independent scope authority. It was revised on 2026-06-12 to match the SPEC's locked decisions: full signer-identity model (consent event + `verification_level`), full status surface (list/filter/per-signer/resend/void), the `sign.*` → cbb-sign DNS migration, and decommissioning the legacy DocuSeal→OneDrive Logic App.

---

## Overview

`cbb-sign` is a CBB-branded document signing platform built on top of self-hosted DocuSeal OSS (private fork). It consists of:

- A web UI + API service for internal users (Entra ID SSO, role-based access)
- A private DocuSeal fork with CBB branding applied
- Post-sign automation: webhook → rename → OneDrive → audit trail
- Gotenberg integration for `.docx` → PDF conversion
- Immutable audit trail: PostgreSQL append-only events + Azure Blob WORM certificates

All services deploy to `cae-cbb-prod` (existing Azure Container Apps environment).

---

## Components & Effort

### 1. DocuSeal Fork — CBB Branding

Scope: logo, colors, email templates, no functional changes.

| Task | Effort |
|---|---|
| Set up upstream remote for official DocuSeal repo | 0.5h |
| Replace logo, favicon, brand colors (CSS/asset overrides) | 2h |
| Customize outbound email templates (signing request, completion) | 2h |
| Build + push branded Docker image to `crcbbprod` ACR | 1h |
| Test branded flow end-to-end | 1h |
| **Subtotal** | **6.5h** |

---

### 2. `cbb-sign` API (Backend)

Language/framework: TBD (modern, maintainable — Node/TypeScript + Express, or Python + FastAPI are the natural fits given the team). Recommend **Python + FastAPI** — clean async, auto-generated OpenAPI docs, minimal boilerplate.

| Task | Effort |
|---|---|
| Project scaffold, Docker, CAE deployment | 2h |
| Entra ID MSAL authentication middleware | 3h |
| Role-based access control (3 Entra groups: sender, template-manager, admin) | 2h |
| DocuSeal API wrapper (templates, submissions, signers, webhooks) | 4h |
| Gotenberg integration (`.docx` → PDF pre-processing) | 1h |
| Template management endpoints (list, create, publish, archive) | 3h |
| Submission endpoints (send template, send one-off) | 3h |
| **Status surface: list + filter (status/signer/template/date), per-signer state, event timeline** (SPEC_api §14.4) | 4h |
| **Status surface actions: resend reminder, void/cancel** (SPEC_api §14.5) | 2h |
| Signer management (ordered/simultaneous, wet-signature override flag) | 2h |
| **Signer identity + consent: `verification_level` field, `signer.consented` event capture/record** (SPEC_audit §20) | 2h |
| Post-sign webhook handler (HMAC verify, dedup, event→state mapping) | 3h |
| Post-sign pipeline: retrieve → hash → rename → file, idempotent + retry/`error` state (SPEC_workflow §11.4) | 2h |
| OneDrive upload via Graph API (filename build, folder routing) | 3h |
| Audit trail: PostgreSQL `cbb_sign` DB schema + append-only event writes | 3h |
| Audit trail: Azure Blob WORM container for completion certificates (cert generation + write) | 3h |
| Notification: SendGrid email on completion (sender-configurable) | 2h |
| Notification: Slack DM on completion (sender-configurable) | 2h |
| **Executed-copy delivery to signers + `copy.delivered` audit event + delivery-signal capture** (SPEC_audit §21.5) | 1.5h |
| `cbb_sign` DB provisioning (new database, migrations, **append-only role grant**) | 1h |
| **Subtotal** | **45.5h** |

---

### 3. `cbb-sign` Web UI (Frontend)

Framework: **Next.js** (React, TypeScript) — internal app, server-side rendering keeps auth simple, good component ecosystem.

| Task | Effort |
|---|---|
| Project scaffold, Docker, CAE deployment, Entra ID SSO (MSAL React) | 3h |
| CBB branding (logo, colors, typography) | 2h |
| Login / role-aware nav | 1h |
| Template library page (list, search, filter by status) | 3h |
| Template send flow (select template → fill signers → pre-fill fields → confirm) | 5h |
| One-off send flow (upload doc → name it → place fields → add signers → confirm) | 5h |
| Status dashboard: list + filters (status/signer/template/date), per-signer state drill-in, resend/void actions (SPEC_api §14.4–14.5) | 5h |
| Admin: user/role management view | 3h |
| Admin: template manager (publish/archive) | 2h |
| Wet signature override UI (mark submission as wet, attach scan) | 2h |
| Responsive, accessible layout | 2h |
| **Subtotal** | **33h** |

---

### 4. Infrastructure (Terraform)

| Task | Effort |
|---|---|
| `cbb-sign` Container App (API) in `cae-cbb-prod` | 1h |
| `cbb-sign` Container App (UI) in `cae-cbb-prod` | 1h |
| Custom domains: `sign.coldboreballisticsllc.com` (UI), `sign-api.coldboreballisticsllc.com` (API) | 1h |
| DNS records in `services/dns` | 0.5h |
| **DocuSeal migration: repoint `sign.*` from DocuSeal to cbb-sign; drop `sign` alias from DocuSeal custom-domain binding** (SPEC §6.6) | 1h |
| **Decommission legacy `logic-docuseal-onedrive-prod` Logic App + `azureblob` connection + failure alert** (SPEC §6.6) | 1h |
| Azure Blob WORM container for audit certificates (immutability/time-based retention policy) | 1h |
| `cbb_sign` PostgreSQL database + **append-only role grant** + KV secrets | 1h |
| **Append-only enforcement: `BEFORE UPDATE OR DELETE` trigger on `audit_event`** (SPEC_audit §21.3) | 0.5h |
| Entra ID app registration for `cbb-sign` (MSAL, single purpose) | 1h |
| **Graph (OneDrive) filing identity for cbb-sign — own managed identity `Files.ReadWrite.All`** (SPEC OQ-2) | 1h |
| Entra groups: `cbb-sign-sender`, `cbb-sign-template-manager`, `cbb-sign-admin` | 0.5h |
| KV secrets: DB URL, DocuSeal API key, webhook secret, Slack token, SendGrid key | 1h |
| **Subtotal** | **12.5h** |

---

### 5. DocuSeal Fork — Wet Signature Provision

Scope: minimal — add a flag to mark a signer slot as "wet signature obtained separately", skip that slot in the signing flow, record in audit trail.

| Task | Effort |
|---|---|
| DocuSeal API: add `wet_signature` field to signer model | 2h |
| DocuSeal: skip notification + signing step for wet-flagged signers | 2h |
| DocuSeal: record wet signature event in completion certificate | 1h |
| Test + rebuild image | 1h |
| **Subtotal** | **6h** |

---

### 6. Testing & Integration

| Task | Effort |
|---|---|
| End-to-end test: template flow (ordered + simultaneous signers) | 3h |
| End-to-end test: one-off flow | 2h |
| End-to-end test: wet signature override | 1h |
| **Integration test: status surface (list/filter, per-signer state, resend, void)** (SPEC_api §14.4–14.5) | 2h |
| Post-sign: verify OneDrive filename, folder routing, WORM cert | 2h |
| Audit trail: verify immutability (UPDATE/DELETE on `audit_event` fails as app role) | 1h |
| Notification: email + Slack DM delivery confirmation | 1h |
| **Subtotal** | **12h** |

---

## Summary

| Component | Original (97.5h) | Revised | Δ |
|---|---|---|---|
| DocuSeal fork — branding | 6.5h | 6.5h | — |
| DocuSeal fork — wet signature | 6h | 6h | — |
| `cbb-sign` API | 36h | 45.5h | +9.5h |
| `cbb-sign` Web UI | 31h | 33h | +2h |
| Infrastructure | 8h | 12.5h | +4.5h |
| Testing & integration | 10h | 12h | +2h |
| **Total** | **97.5h** | **115.5h** | **+18h** |

**What grew and why** (all traceable to SPEC decisions locked 2026-06-12):

- **API +9.5h** — full status surface (list/filter, per-signer state, event timeline, resend, void) vs. the original thinner "status, list"; the consent event + `verification_level` capture (SPEC_audit §20); a more rigorous webhook/post-sign pipeline (HMAC verify, dedup, idempotent retry/`error` state); and executed-copy delivery to signers as an audit-recorded `copy.delivered` event (SPEC_audit §21.5, owner OQ-11a).
- **UI +2h** — the status dashboard becomes a real operational surface (filters + per-signer drill-in + resend/void), not a simple history list.
- **Infra +4.5h** — the DocuSeal `sign.*` → cbb-sign DNS migration, decommissioning the legacy DocuSeal→OneDrive Logic App, cbb-sign's own Graph filing identity, and DB-level append-only enforcement (role grant + immutability trigger).
- **Testing +2h** — status-surface integration coverage.

> **Cost/scope note (acquisition lens):** This is a ~17% effort increase over the first estimate, entirely from making cbb-sign a usable operational tool (real tracking, stronger legal record) rather than a fire-and-forget sender. The SPEC and this LOE are now consistent — a buyer's DD will not find scope drift between the spec and the estimate. This is a build-effort estimate only; it does **not** include recurring Azure resource cost, which requires its own written cost-impact analysis and approval before any `terraform apply` (SPEC §6, cost-approval directive).

---

## Open Items / Assumptions

Items below the line are **resolved by the SPEC**; remaining open questions are tracked as `OQ-*` in the SPEC (`SPEC.md`, `SPEC_api.md`, `SPEC_audit.md`).

**Resolved (SPEC, 2026-06-12):**
- **Domain names** — confirmed: `sign.*` → cbb-sign UI, `sign-api.*` → cbb-sign API, `esign.*` stays DocuSeal (SPEC §6.2). The `sign.*` collision with DocuSeal is resolved by migration (SPEC §6.6).
- **API framework** — FastAPI, confirmed (SDLC §2.1).
- **Multi-signer filename** — confirmed: one external signer → their name (`LastFirst`); >1 external → `MultiSigner`; all-internal → `Internal` (SPEC_workflow §11.2). (Supersedes the earlier "≤3 / truncate" assumption.)
- **Wet signature scan storage** — confirmed: `Awaiting_Wet_Signature/` sibling of the signed parent (SPEC_workflow §11.2).
- **Filing mechanism** — confirmed: cbb-sign webhook is the sole filer; legacy Logic App decommissioned (SPEC §6.6).

**Open (tracked in SPEC as OQ-*):**
- OQ-1 Sender request-visibility scope in `0.1.0` (own vs all).
- OQ-2 cbb-sign Graph/OneDrive identity (own MI vs reuse DocuSeal's app reg).
- OQ-6 One-off field-placement UX (DocuSeal editor vs wrapper-native) — affects the one-off UI line.
- OQ-9 Consent mechanism in the DocuSeal fork (built-in vs added) — affects fork scope/effort.
- OQ-10/11/12 Certificate format, executed-PDF-under-WORM scope, and WORM retention period.

**Not in this LOE:**
- **Upstream DocuSeal pull cadence** — periodic maintenance, not estimated.
- **Security review** — the cbb-sign security review must be completed before `1.0.0` go-live (SDLC §7.1); the existing DocuSeal security review conditions carry forward. Not in this build estimate.

---

## Document Control

| Field | Value |
|---|---|
| **Document owner** | `jschwefel@coldboreballisticsllc.com` |
| **Classification** | Confidential (`03_-_Confidential/`) |
| **Location** | `OneDrive/CBB_Sign/03_-_Confidential/Infrastructure/CBB_Sign_LOE.md` |
| **Cross-references** | `CBB_Sign_SDLC.md`; `SPEC.md`/`SPEC_workflow.md`/`SPEC_api.md`/`SPEC_audit.md` |
| **Created** | 2026-06-12 |
| **Last updated** | 2026-06-12 |

---

## Revision History

| Rev | Date | Changed By | Summary |
|-----|------|------------|---------|
| DRAFT | 2026-06-12 | Claude Code | Initial LOE — 97.5h across 6 components. |
| DRAFT | 2026-06-12 | Claude Code (Opus 4.8) | Reconciled with the cbb-sign SPEC. Total 97.5h → **114h** (+16.5h): API 36→44h (full status surface + consent/`verification_level` + hardened webhook/pipeline), UI 31→33h (operational status dashboard), infra 8→12.5h (DNS migration, Logic App decommission, cbb-sign Graph identity, DB append-only enforcement), testing 10→12h (status-surface integration). Resolved domain/filename/filing/wet-scan assumptions per SPEC; remaining questions tracked as SPEC `OQ-*`. Added Document Control + Revision History blocks. |
| DRAFT | 2026-06-12 | Claude Code (Opus 4.8) | All 12 OQs resolved by owner. **114h → 115.5h** (+1.5h API): added executed-copy delivery to signers as audit-recorded `copy.delivered` event (owner OQ-11a). Resolved decisions baked into SPEC. Generated two CBB-wide CLAUDE.md directives (OQ-2 → "Service Identity Ownership"; OQ-3 → "Credential Sharing — VERBOTEN"); `app-docuseal-sharepoint-prod` now deleted at cutover (cbb-sign uses own MI). Input limits + upload-TTL made configurable (25MB/100pp; 72h). WORM retention starts at **1yr** (defensible policy; lengthen later if