Commit graph

126 commits

Author SHA1 Message Date
justin
40844b2aff Add generic eSign portal for all compliance document types
Reusable signing flow: service handler generates document → inserts
esign_records row → emails JWT link → client reviews PDF + signs →
API stores signature + resumes pipeline. Works for RMD, CPNI, CALEA,
499-A engagement, discontinuance, CRTC, and any future doc types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-04 10:45:37 -05:00
justin
37a22cf474 Delete 499-A checklist generator — intake wizard handles data collection
The checklist was a manual-process artifact that listed what info the
client needed to gather. Since all data is now collected through the
intake wizard and CDR upload, the checklist is unnecessary. Removed
from the 499-A handler's prep packet and deleted the 1,326-line
generator file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-04 09:10:50 -05:00
justin
463c180444 Add shared DOCX style module + campaign tools
_styles.py: Centralized typography, spacing, and formatting for all
26 DOCX generators. Calibri 9.5pt body, 1.15 line spacing, navy
headings, consistent signature blocks, page numbers, PW footer.
All generators will be migrated to use this instead of defining
their own styles.

Campaign tools:
- campaign_template.html: Styled email template for Listmonk campaigns
- populate_deficiency_list.py: Populates Listmonk with FCC deficiency data
- send_test_campaigns.py: Sends test emails with real carrier data

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-04 08:52:07 -05:00
justin
c9881868dd Add Telegram notification on every new paid order
Sends to the monitoring bot immediately when payment is confirmed:
- Customer name and email
- Service/slug ordered
- Total amount (includes all fees: service + formation + state + addons)
- Payment method
- Order number and type

Fire-and-forget — never blocks the payment flow.
Requires TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID env vars on API container.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-04 07:32:42 -05:00
justin
7d783bdb39 Reprice services: à la carte ~$2,000 vs FCC Carrier Reg bundle $1,299
Individual services repriced so buying separately costs ~$2K, making
the $1,299 FCC Carrier Registration a clear 35% savings:

- CORES/FRN: $99 → $149
- Form 499 Initial: $299 → $349
- D.C. Registered Agent: $99 → $149/yr
- RMD: $219 → $249 (+ $100 FCC fee)
- CPNI: $149 → $199
- CALEA SSI: $299 → $799 (includes consulting work)
- BDC Broadband: $199 → $249
- BDC Voice: $149 → $199
- BDC Both: $299 → $349

Retired New Carrier Bundle ($1,799) — redirected to FCC Carrier
Registration ($1,299) which includes more services and the wizard.

Updated prices in: API catalog, batch order page, compliance checker CTA.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-04 06:23:19 -05:00
justin
16b5c2da0b Fix BDC pricing in compliance checker CTA
- BDC both (broadband+voice): $299 (was $349, now matches bdc-filing API price)
- BDC broadband-only: $199 (uses bdc_broadband slug)
- BDC voice-only: $149 (uses bdc_voice slug — was showing $199)
- Added _bdcVariant tracking so CTA uses correct slug per user answer
- Reset variant on undo

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-04 06:17:06 -05:00
justin
47ca1bf10f Production readiness fixes: 3 critical + 2 high-priority
Critical #1 — CRTC: Fix undefined 'province' variable (canada_crtc.py:1322)
  Crashes every order at Step 6 document generation. Replaced with
  order_data.get("custom_incorporation_province", "BC").

Critical #2 — FCC Carrier Reg: Add State PUC state picker
  The order page collected "1/few/nationwide" but API expected an array
  of state codes. Added a multi-state checkbox grid that appears when
  State PUC add-on is checked. Sends puc_states: ["CA","NY",...] in
  service_wizard. Price updates per-state ($399 × count).

Critical #3 — Compliance: Add REQUIRED_FIELDS for fcc-499q and
  fcc-499a-discontinuance. Without these, intake validation was
  completely skipped — invalid data accepted silently.

High #4 — FCC Carrier Reg: Don't mark D.C. Agent complete
  prematurely. Was calling _update_step() right after creating the
  admin todo. Now waits for admin to confirm NW order is placed.

High #5 — Compliance: Add fcc-499q and fcc-499a-discontinuance to
  REQUIRES_ENTITY_FRN set. Both require FRN for USAC filing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-04 05:28:13 -05:00
justin
78c04b8bc3 Add Playwright failure monitoring: Telegram alerts + screenshots + health check
When any Playwright submission fails (selector not found, timeout, etc.):
1. Full-page screenshot captured and uploaded to MinIO
2. Telegram alert sent immediately with error details + screenshot link
3. Email alert to ops with same info
4. Admin todo includes screenshot MinIO path for debugging
5. Client order stays pending for manual completion

Proactive selector health check (daily 7am CT cron):
- Navigates to each portal (FCC RMD, USAC E-File, FCC CPNI/ECFS)
- Verifies all critical selectors are still present in the DOM
- If selectors are missing (UI changed): alerts via Telegram + email
  BEFORE any real client order fails
- Reports which service slugs are affected

Integrated into:
- RMD filing handler (fccprod.servicenowservices.com)
- Form 499-A handler (forms.universalservice.org)
- Form 499-Q handler (already had error handling)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-04 02:44:02 -05:00
justin
3e04edd384 Fix handler return types: return file path lists not dicts
job_server.py expects process() to return a list of file paths for
MinIO upload. The 499-Q and discontinuance handlers were returning
dicts like {'status':'admin_review'} which caused the job_server to
iterate dict keys as file paths -> 'File not found: status'.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-04 02:23:21 -05:00
justin
0bdaa4c373 Fix auto_filing: check env var before ERPNext to avoid hanging on dev
When AUTO_FILING_ENABLED is explicitly set as an env var, skip the
ERPNext API call entirely. The ERPNext client hangs indefinitely
when the host is unreachable (dev workers can't reach prod ERPNext),
blocking all compliance handlers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 23:22:39 -05:00
justin
9be495dbb9 Fix import: BaseServiceHandler not BaseComplianceHandler, add _create_admin_todo
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 23:14:35 -05:00
justin
f30b0383a9 Automate 499-Q USAC filing + discontinuance letter auto-email
499-Q Handler:
- Auto-filing toggle integration (same as 499-A)
- Playwright USAC E-File submission for quarterly form
- Revenue field filling (4 categories)
- Confirmation number capture + PDF save
- Client receives "data received" email immediately, then
  "filed successfully" email with confirmation number after submission
- Falls back to admin todo if Playwright/session unavailable

Discontinuance Handler:
- Auto-emails deactivation letter to USAC (Form499@usac.org)
  with DOCX attachment + entity summary in body
- CC to admin email for records
- Dev mode: redirects USAC email to admin instead
- Client confirmation email with process timeline

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 23:04:15 -05:00
justin
a404cb1b57 Add USAC Filer ID Deactivation Letter template
Formal letter addressed to USAC Contributor Operations requesting
deactivation of a 499 Filer ID. Covers:
- Entity identification (name, Filer ID, FRN, EIN)
- Reason for deactivation + termination date
- Final 499-A status (zero-revenue included OR filed separately)
- Successor entity info (if applicable)
- Outstanding balance acknowledgment
- Related filings confirmation (RMD, CPNI, BDC)
- Officer signature block
- Entity summary box

Handler updated to:
- Generate the letter via document_gen template
- Upload DOCX to MinIO (compliance/{order_number}/)
- Reference the letter in admin todo

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 06:15:26 -05:00
justin
eee2aa497b Fix long-running PG transactions in RMD scrapers
Both scrapers held a cursor/transaction open while doing slow HTTP
requests to FCC ServiceNow and company websites, causing
"idle in transaction" for 10+ minutes and triggering the
PostgresSlowQueries alert.

Fix: fetch all row IDs upfront, commit the read transaction
immediately, then process each row with its own short
UPDATE+COMMIT cycle. No long-lived transactions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 04:13:33 -05:00
justin
5e74c1dcb9 Split discontinuance CTA into two options based on revenue
When user selects "cancel registration" in the compliance checker:
- Option 1: "499-A Discontinuance (incl. zero-revenue filing)" $299
  For carriers with no revenue — includes final zero-revenue 499-A
  + deactivation letter + CORES update
- Option 2: "499-A Filing + Discontinuance" $798 ($499+$299)
  For carriers with actual revenue — full 499-A filed separately
  + deactivation process

Standalone discontinuance ($299) is for carriers already current
on filings who just want to close out.

Handler detects whether zero-revenue filing is included vs
handled by a separate full 499-A order.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 03:39:52 -05:00
justin
0c5f9d12c1 Update discontinuance handler with correct FCC process
Per 2026 FCC Form 499-A Instructions:
- Final 499-A CAN have actual revenue (not required to be zero)
  — reports revenue for the period the company was in service
- Deactivation is a SEPARATE step: submit letter to USAC with
  termination date and successor entity info, within 30 days
- Line 603: check TRS/LNP/NANPA exemption boxes, write
  "Not in business as of [date]"
- USAC processing takes 60-90 days
- CORES must be updated to reflect inactive status

Handler now creates admin todo with 4-step process:
1. File final 499-A with actual/zero revenue
2. Submit USAC deactivation letter (Form499@usac.org)
3. Update CORES registration
4. Confirm CPNI/RMD/BDC discontinued

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 03:02:23 -05:00
justin
0fc318cb38 Add 499-Q intake page, 499-Q handler, and 499-A discontinuance handler
499-Q Quarterly Filing:
- Intake page at /order/fcc-499q with simplified revenue form
  (4 fields: carrier's carrier inter/intra, end-user inter/intra)
- Zero-revenue confirmation checkbox
- Handler creates admin todo with filing details + sends client email
- Registers as fcc-499q in SERVICE_HANDLERS

499-A Discontinuance:
- Handler creates admin todo with step-by-step USAC instructions
  (file zero-revenue 499-A, request account closure, confirm CPNI/RMD)
- Sends client confirmation email explaining the process
- Compliance checker CTA: when user selects "No — cancel registration"
  in the 499-A toggle, shows discontinuance option ($299) instead of
  standard filing
- Order page maps form_499a_disc to fcc-499a-discontinuance slug

Compliance checker intelligence:
- 499-A toggle tracks _499aVariant (null/zero/discontinuance)
- CTA adapts: revenue=standard 499-A, zero=zero-revenue, cancel=discontinuance
- Reset clears variant flag

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 02:34:18 -05:00
justin
572f0cbf93 Implement 499-Q quarterly filing lifecycle
After 499-A+Q bundle is filed, the handler now creates actual
compliance_orders for each remaining quarterly 499-Q filing:

Schedule: Q1 due Feb 1, Q2 due May 1, Q3 due Aug 1, Q4 due Nov 1

Each quarterly order:
- Created as paid (covered by bundle price)
- Has due_date, quarter, period_end_date in intake_data
- Links to parent 499-A order
- Tracks reminder status (30d/14d/7d sent flags)

Notification worker (quarterly_499q_notify.py):
- Runs daily at 8am CT via systemd timer
- Sends HTML reminder emails at 30, 14, 7 days before due
- Email includes intake link for client to submit quarterly data
- Late warning at 7 days: "USAC may estimate higher contributions"
- Idempotent: won't re-send same reminder level

Added fcc-499q service slug ($0, not sold standalone).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 02:28:04 -05:00
justin
3e04a8fc16 Add zero-revenue 499-A filing at $179
New service slug fcc-499a-zero for carriers with no telecom revenue:
- $179 instead of $499 (no revenue analysis needed)
- Minimal intake: entity, officer, filer ID, filing type only
- Skips revenue schedules (blocks 3-4), USF calculations (block 5),
  traffic study upload, and revenue workbook generation
- Fills blocks 1-2 and 6 only, all revenue lines left as zero

Compliance checker: shows both options (mutually exclusive checkboxes)
Order page: maps form_499a_zero to fcc-499a-zero slug
Handler: detects slug and skips revenue pipeline
DC Agent shown when either 499-A variant is checked

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 02:04:18 -05:00
justin
5dabac856d Revert quick=1, keep 60s timeout — full RMD quality checks needed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 01:52:34 -05:00
justin
aef3cc7bbe Fix FCC timeout: add quick=1 param and increase timeout to 60s
FCC APIs (CORES, ServiceNow) are responding in 28-45 seconds.
The 30s frontend timeout was being hit ~50% of the time. Added
quick=1 to skip slow Playwright corp checks and increased timeout
to 60s as safety margin.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 01:51:55 -05:00
justin
d4af416b8e Remove 'compliance advice' from disclaimer — we do provide compliance services
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-03 01:49:09 -05:00
justin
1d4d562c0f Fix BDC CTA: add red (voice+broadband) option at higher price
When both voice and broadband are selected, BDC status is red but
the CTA had no case for red — nothing was added to the order.
Now: red = "BDC Broadband + Voice Filing" at $349 (both filings),
yellow = single filing at $199 (unchanged).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-02 09:24:53 -05:00
justin
95749b138c Fix BDC toggle in Astro page: ask voice first, then broadband
The BDC toggle was in the static HTML file (site/public/tools/)
which is overridden by the Astro page. Fixed the ACTUAL page at
site/src/pages/tools/fcc-compliance-check.astro.

Two-step flow: asks about retail voice first, then broadband.
Results: voice+broadband=red (both filings), voice-only=yellow
(BDC Voice), broadband-only=yellow (BDC Broadband), neither=green.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-02 09:20:27 -05:00
justin
d03135e9d0 Hide STIR/SHAKEN from compliance checker, remove toggle
STIR/SHAKEN card creates confusion with OCN bundling on the order
page — if carried over, it doesn't auto-select OCN. Self-reported
RMD status also doesn't reliably indicate actual compliance.
Reverted to hidden (computed internally but not shown to users).
Removed STIR toggle and pending-question watcher for it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-02 08:55:10 -05:00
justin
0f9b473030 Fix BDC check: ask voice first, rename card, support voice-only carriers
BDC Voice Subscription filing is required for all retail voice
providers (VoIP, CLEC, wireline) even without broadband service.
The compliance checker was asking about broadband first, which
caused voice-only carriers to overlook their BDC Voice obligation.

- Renamed card: "BDC Filing (Broadband + Voice Subscription Data)"
- Reversed question order: ask retail voice first, then broadband
- "No" for voice now says "No, or wholesale only" (wholesale exempt)
- Voice-only retail carriers correctly flagged for BDC Voice filing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-02 07:28:09 -05:00
justin
92427291e6 Fix ContainerHighMemory alert: skip containers with no memory limit
Containers without a memory limit have spec_memory_limit_bytes=0,
causing division to produce +Inf which always fires. Added guard:
only alert when a limit is actually set (> 0).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 03:54:16 -05:00
justin
15f5c267e7 Fix dashboard stale series + enable Prometheus admin API
Dashboard queries now use max() to pick UP value when old stale
probe targets coexist with new ones. Prometheus admin API enabled
for future TSDB cleanup of stale series.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 03:43:42 -05:00
justin
3194c71495 Fix Forgejo probe: use HTTPS public URL (port 3000 conflicts with Grafana internally)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 03:38:36 -05:00
justin
b190bcef92 Fix ERPNext and Forgejo probes
- ERPNext: custom blackbox module with Host: performancewest.net header
  (ERPNext multitenancy requires site name in Host for routing)
- Forgejo: add extra_hosts to blackbox-exporter so it can resolve
  host.docker.internal to reach forgejo on port 3000
- Blackbox http_erpnext module: sets Host header, expects 200

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 03:35:45 -05:00
justin
f856434642 Fix service probes: correct endpoints and permissive HTTP module
- Workers: use http_internal module (HTTP/1.0 SimpleHTTPServer)
- ERPNext: use /api/method/ping, accept 401/403 (still means alive)
- Listmonk: use /health not /api/health (403 without auth)
- Forgejo: port 3000 not 3030
- Dev API: probe via HTTPS public URL (blackbox can't reach Docker)
- Added http_internal blackbox module accepting HTTP/1.0 + 401/403

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 03:33:48 -05:00
justin
2f9005693e Add deep service health monitoring for all PW dependencies
Each service gets its own Prometheus probe verifying actual functionality:
- API: /status endpoint (checks DB connectivity, returns 503 if down)
- Workers: /health endpoint (job server responsive)
- ERPNext: API method call (MariaDB + Redis + app all working)
- MinIO: /minio/health/live (storage accessible)
- Listmonk: /api/health (email service + DB)
- Ollama: root endpoint (LLM inference available)
- Umami: /api/heartbeat (analytics tracking)
- Forgejo: root page (git server accessible)
- PostgreSQL: pg_up metric from postgres-exporter
- All HTTPS endpoints: SSL + reachability from outside

Service-specific alerts with context:
- API down = DB may be unreachable
- Workers down = compliance orders not processing
- ERPNext down = CRM inaccessible
- MinIO down = document storage unavailable

Custom Grafana dashboard: "Performance West — Services Overview"
- Service status grid (UP/DOWN with colors)
- Response time charts (internal + HTTPS)
- SSL certificate expiry gauges
- Container CPU/memory per service
- PostgreSQL connections, nginx req/s, active alerts

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 03:30:23 -05:00
justin
cc463a662f Fix MinIO health probe: use internal Docker URL instead of public
MinIO returns 403 when accessed via minio.performancewest.net because
it interprets the Host header as a bucket name. Switch blackbox probe
to internal http://minio:9000/minio/health/live which works correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 03:26:46 -05:00
justin
0a31313956 Fix nginx-exporter: back to bridge network with host.docker.internal
host network mode prevented Prometheus from reaching the exporter.
Switched back to bridge with extra_hosts + explicit port mapping.
Added timeout flag to prevent hanging on stub_status fetch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 03:21:27 -05:00
justin
433827138b Fix nginx-exporter: use host network mode for direct stub_status access
nginx-exporter couldn't reach host nginx via host.docker.internal
(connection timeout). Switch to network_mode: host so it can access
127.0.0.1:8888 directly. Prometheus scrapes via host.docker.internal
with extra_hosts mapping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 03:19:57 -05:00
justin
27cc925c4d Fix nginx-exporter port and add alertmanager scrape target
- nginx stub_status moved to port 8888 (port 80 was being caught
  by other server blocks and returning 301)
- nginx-exporter updated to scrape :8888
- Added alertmanager scrape job to Prometheus config (was missing,
  so alertmanager dashboard had no data)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 03:17:31 -05:00
justin
b38b1af872 Disable Grafana brute force lockout during initial setup
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 03:11:30 -05:00
justin
b298ec12b7 Remove fixed uid from Grafana datasource provisioning — Grafana 13 rejects it on fresh boot
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 03:09:10 -05:00
justin
fc324cf7b9 Fix Grafana datasource UID to match dashboard references
Community dashboards reference datasource uid=prometheus but the
auto-generated UID was random. Pin to uid=prometheus for compatibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 03:07:03 -05:00
justin
a4a5500bfc Add Prometheus + Grafana + Alertmanager monitoring stack
Full observability stack with Telegram alerting:

Components:
- Prometheus: metrics collection, 90-day retention
- Grafana: dashboards at monitoring.performancewest.net
- Alertmanager: routes alerts to Telegram bot
- node-exporter: OS metrics (CPU, RAM, disk, network)
- cAdvisor: container metrics (CPU, memory, restarts)
- postgres-exporter: PostgreSQL connection/query metrics
- nginx-exporter: request rate, 5xx errors, connections
- blackbox-exporter: HTTP/TCP endpoint probing + SSL cert checks

Alert rules:
- Service down (HTTP probe, TCP port, container missing)
- Container restart loops
- High CPU/memory/disk/load
- PostgreSQL down or high connections
- SSL cert expiring (14d warning, 3d critical)
- Slow HTTP responses, high 5xx rate

Blackbox probes all public endpoints:
  performancewest.net, api, dev, crm, lists, analytics,
  minio, crypto, pay

Telegram alerts: critical=1h repeat, warning=6h repeat,
  auto-resolve notifications

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-01 02:08:39 -05:00
justin
97e8664cbf Add security-updates Ansible role for automated patching
Comprehensive security update automation:

1. Debian OS (unattended-upgrades) — tightened to security-only:
   - Removed general Debian updates (prevents feature/breaking changes)
   - Only Debian-Security origins auto-installed
   - Email admin on every upgrade via ops@performancewest.net
   - Auto-reboot at 4 AM if kernel update requires it
   - needrestart auto-restarts services after library updates

2. Docker CE — major version guard:
   - Patch updates within pinned major version auto-applied
   - Major version jumps held + admin alerted for manual review
   - docker-ce, docker-ce-cli, containerd.io all version-guarded

3. Container base images — daily at 3:30 AM:
   - Pulls latest base images for all docker-compose services
   - Compares image digests — only rebuilds if changed
   - Restarts only affected services (not full stack)
   - Alerts admin on rebuild failures requiring manual intervention
   - Covers both prod and dev compose projects

4. k3s — weekly Sunday at 3:45 AM:
   - Patch updates within current minor auto-applied
   - Minor/major upgrades alert admin for manual review
   - Verifies node Ready status after update
   - Alerts on failures with investigation instructions

5. Admin notifications via SMTP:
   - [INFO] for successful patches
   - [WARNING] for available major upgrades needing review
   - [CRITICAL] for failures requiring immediate intervention
   - Falls back to syslog if SMTP unavailable

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-30 01:24:57 -05:00
justin
611b8a9600 Validate Q1b and Q2 before proceeding to Step 2
Users could skip Q1b (customer type) and Q2 (voice delivery) and
hit Next — the wizard silently defaulted to retail. Now validates:
- Q1b must be answered (customer type selected)
- Q2 must be answered if voice is checked and not wholesale-only

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 11:46:11 -05:00
justin
fbf3b8a1ea Add terminate-only STIR/SHAKEN option across RMD pipeline
STIRShakenStep intake:
- New "Terminate only" option for carriers that only receive pre-signed
  calls and don't originate
- Contextual hints for each option explaining requirements
- Show/hide vendor and upstream fields based on selection

RMD letter generator:
- New terminate_only section explaining verification-only posture,
  citing 47 CFR § 64.6301 (signing obligation on originating provider)
- Added to needs_exhibit_a list

RMD Exhibit A generator:
- New terminate_only STIR/SHAKEN paragraph with SBC verification language
- Fixed scope paragraph: wholesale/facilities carriers no longer get
  "small provider without Class 4 switch" boilerplate
- Fixed OCN paragraph: wholesale carriers get neutral wording instead
  of "no OCN required for small retail provider"

RMD filing handler:
- Maps stir_shaken_status to rmd_option for Exhibit A generation
- Passes entity metadata (ocn, wholesale, gateway, contact) to generator
- Maps terminate_only → partial_implementation for FCC RMD form radio

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 10:59:28 -05:00
justin
050b19a43a Enable STIR/SHAKEN card in compliance checker with originate/terminate toggle
- Uncomment STIR/SHAKEN check in fcc-lookup.ts — shows self-reported
  implementation status from RMD filing
- Add toggle: "Do you originate calls or only terminate?"
  - Terminate only → green, signing cert not required, file RMD as
    partial implementation
  - Originate or both → red, must have own STI certificate as of
    June 2025
- Toggle integrates with pending-question system (CTA waits for answer)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 10:55:00 -05:00
justin
b02b5b4c1f Add STIR/SHAKEN originate vs terminate guidance in Q3
Wholesale providers that only receive/terminate pre-signed calls don't
need a STIR/SHAKEN signing certificate. Info box explains: originating
providers must sign with own cert (as of June 2025), but
terminating-only providers just verify signatures (software config)
and file RMD as "partial implementation."

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 10:52:18 -05:00
justin
3ea47b52ed Fix Q5 showing retail variant for wholesale carriers
Q5 was shown immediately on voice/broadband checkbox, before Q1b
(customer type) was answered — always defaulting to retail variant.
Now Q5 only appears after Q1b is answered, and the correct variant
(retail vs wholesale) is set at that point.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 10:48:53 -05:00
justin
653837617e Clarify non-interconnected VoIP with examples
Explain that non-interconnected VoIP means voice apps without phone
numbers (e.g. Microsoft Teams, Discord) to distinguish from
interconnected VoIP which connects to the PSTN.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 10:08:51 -05:00
justin
dec69ffc0e Add contact-us notice for non-standard service types
Non-interconnected VoIP, satellite, paging, private radio, and other
specialized services have different registration requirements. Show
a yellow info box under Q1 directing these users to contact us for
a custom assessment instead of using the wizard.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 10:02:02 -05:00
justin
337528b08a Split Q5 into retail vs wholesale variants
Retail carriers: ask where end-user customers are located, explain
state PUC nexus (registration triggered by customer location, not
incorporation). Wholesale carriers: simplified question about carrier
customer regions, explains they generally don't need state PUC
registration since the retail carrier holds the state obligation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 10:01:46 -05:00
justin
9c4d65c7a9 Skip Q2 voice delivery for wholesale-only carriers
Wholesale voice carriers run their own switching by definition — the
Q2 options (reseller, UCaaS, own switch) are retail delivery models.
When wholesale is selected in Q1b, Q2 is hidden and Q3 (infrastructure
needs: LCR, DIDs, interconnection, STIR/SHAKEN) is shown directly.
voiceDelivery is auto-set to own_switch for wholesale.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-29 09:59:03 -05:00