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>
This commit is contained in:
parent
050b19a43a
commit
fbf3b8a1ea
4 changed files with 123 additions and 17 deletions
|
|
@ -138,6 +138,25 @@ def _stir_shaken_paragraph(
|
||||||
f"high-volume origination, or facilities-based IP origination "
|
f"high-volume origination, or facilities-based IP origination "
|
||||||
f"infrastructure."
|
f"infrastructure."
|
||||||
)
|
)
|
||||||
|
elif option == "terminate_only":
|
||||||
|
paras.append(
|
||||||
|
f"{entity_name} operates as a terminating provider and does not "
|
||||||
|
f"originate calls onto the public switched telephone network. "
|
||||||
|
f"{entity_abbr} receives exclusively pre-authenticated traffic "
|
||||||
|
f"from originating and intermediate providers. As a "
|
||||||
|
f"terminating-only carrier, {entity_abbr} is not required to "
|
||||||
|
f"maintain its own STI certificate or SPC token for call signing."
|
||||||
|
)
|
||||||
|
paras.append(
|
||||||
|
f"{entity_abbr} verifies STIR/SHAKEN attestation information on "
|
||||||
|
f"all inbound SIP INVITE messages using standard verification "
|
||||||
|
f"service (VS) functionality configured on its session border "
|
||||||
|
f"controllers. {entity_abbr} certifies partial STIR/SHAKEN "
|
||||||
|
f"implementation (Option 2 in the RMD) reflecting its "
|
||||||
|
f"verification-only posture. Per FCC rules, the obligation to "
|
||||||
|
f"sign calls falls on the originating provider, not the "
|
||||||
|
f"terminating provider."
|
||||||
|
)
|
||||||
elif option == "option3":
|
elif option == "option3":
|
||||||
paras.append(
|
paras.append(
|
||||||
f"{entity_name} certifies no STIR/SHAKEN signing implementation "
|
f"{entity_name} certifies no STIR/SHAKEN signing implementation "
|
||||||
|
|
@ -203,12 +222,18 @@ def _scope_paragraph(
|
||||||
f"{entity_abbr} does not accept foreign-originated traffic and "
|
f"{entity_abbr} does not accept foreign-originated traffic and "
|
||||||
f"operates solely with domestic U.S. NANP resources."
|
f"operates solely with domestic U.S. NANP resources."
|
||||||
)
|
)
|
||||||
parts.append(
|
if is_wholesale or carrier_role in ("wholesale_domestic", "gateway", "facilities"):
|
||||||
f"As a small provider without its own Class 4 switch or outbound "
|
parts.append(
|
||||||
f"origination platform, {entity_abbr} relies on trusted underlying "
|
f"{entity_abbr} operates its own network infrastructure for "
|
||||||
f"carriers for DID origination, call termination, and STIR/SHAKEN "
|
f"voice service delivery."
|
||||||
f"attestation/signing where applicable."
|
)
|
||||||
)
|
else:
|
||||||
|
parts.append(
|
||||||
|
f"As a small provider without its own Class 4 switch or outbound "
|
||||||
|
f"origination platform, {entity_abbr} relies on trusted underlying "
|
||||||
|
f"carriers for DID origination, call termination, and STIR/SHAKEN "
|
||||||
|
f"attestation/signing where applicable."
|
||||||
|
)
|
||||||
parts.append(
|
parts.append(
|
||||||
f"This Robocall Mitigation Plan outlines {entity_abbr}'s measures to "
|
f"This Robocall Mitigation Plan outlines {entity_abbr}'s measures to "
|
||||||
f"detect, prevent, and mitigate unlawful robocalls in compliance "
|
f"detect, prevent, and mitigate unlawful robocalls in compliance "
|
||||||
|
|
@ -382,12 +407,18 @@ def generate_exhibit_a(
|
||||||
if ocn:
|
if ocn:
|
||||||
_add_body(doc, f"OCN: {ocn}")
|
_add_body(doc, f"OCN: {ocn}")
|
||||||
else:
|
else:
|
||||||
_add_body(doc, (
|
if is_wholesale or carrier_role in ("wholesale_domestic", "gateway"):
|
||||||
f"{entity_abbr} does not possess an Operating Company Number "
|
_add_body(doc, (
|
||||||
f"(OCN). Per FCC guidance, no OCN is required for a small "
|
f"{entity_abbr} does not currently possess an Operating "
|
||||||
f"retail provider without local exchange carrier status; "
|
f"Company Number (OCN). \"No\" is selected on the RMD form."
|
||||||
f"\"No\" is selected on the RMD form."
|
))
|
||||||
))
|
else:
|
||||||
|
_add_body(doc, (
|
||||||
|
f"{entity_abbr} does not possess an Operating Company Number "
|
||||||
|
f"(OCN). Per FCC guidance, no OCN is required for a small "
|
||||||
|
f"retail provider without local exchange carrier status; "
|
||||||
|
f"\"No\" is selected on the RMD form."
|
||||||
|
))
|
||||||
|
|
||||||
_add_body(doc, "Principals, Affiliates, Subsidiaries, and Parent Companies:", bold=True)
|
_add_body(doc, "Principals, Affiliates, Subsidiaries, and Parent Companies:", bold=True)
|
||||||
_add_bullets(doc, principals or [f"{contact_name or entity_name} (sole principal)"])
|
_add_bullets(doc, principals or [f"{contact_name or entity_name} (sole principal)"])
|
||||||
|
|
|
||||||
|
|
@ -463,6 +463,7 @@ def generate_rmd_letter(
|
||||||
|
|
||||||
needs_exhibit_a = stir_shaken_status in (
|
needs_exhibit_a = stir_shaken_status in (
|
||||||
"partial_implementation",
|
"partial_implementation",
|
||||||
|
"terminate_only",
|
||||||
"robocall_mitigation_only",
|
"robocall_mitigation_only",
|
||||||
"exempt_small_carrier",
|
"exempt_small_carrier",
|
||||||
)
|
)
|
||||||
|
|
@ -1071,6 +1072,24 @@ def _build_stir_shaken_section(
|
||||||
italic=True,
|
italic=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
elif stir_shaken_status == "terminate_only":
|
||||||
|
b.body(
|
||||||
|
f"{entity_name} operates exclusively as a terminating provider "
|
||||||
|
f"and does not originate calls onto the public switched telephone "
|
||||||
|
f"network. {entity_name} receives pre-authenticated traffic from "
|
||||||
|
f"originating and intermediate providers and verifies STIR/SHAKEN "
|
||||||
|
f"attestation information on all inbound SIP INVITE messages."
|
||||||
|
)
|
||||||
|
b.body(
|
||||||
|
f"As a terminating-only carrier, {entity_name} is not required to "
|
||||||
|
f"maintain its own STI certificate or SPC token for call signing. "
|
||||||
|
f"Per FCC rules (47 CFR § 64.6301), the obligation to sign calls "
|
||||||
|
f"falls on the originating provider, not the terminating provider. "
|
||||||
|
f"{entity_name} certifies partial STIR/SHAKEN implementation "
|
||||||
|
f"(reflecting its verification-only posture) and has implemented "
|
||||||
|
f"a robocall mitigation program as described in Exhibit A."
|
||||||
|
)
|
||||||
|
|
||||||
elif stir_shaken_status == "robocall_mitigation_only":
|
elif stir_shaken_status == "robocall_mitigation_only":
|
||||||
b.body(
|
b.body(
|
||||||
f"{entity_name} has not implemented STIR/SHAKEN caller ID "
|
f"{entity_name} has not implemented STIR/SHAKEN caller ID "
|
||||||
|
|
|
||||||
|
|
@ -299,6 +299,7 @@ class RMDFilingHandler(BaseServiceHandler):
|
||||||
stir_status = entity.get("stir_shaken_status", "complete_implementation")
|
stir_status = entity.get("stir_shaken_status", "complete_implementation")
|
||||||
if stir_status in (
|
if stir_status in (
|
||||||
"partial_implementation",
|
"partial_implementation",
|
||||||
|
"terminate_only",
|
||||||
"robocall_mitigation_only",
|
"robocall_mitigation_only",
|
||||||
"exempt_small_carrier",
|
"exempt_small_carrier",
|
||||||
):
|
):
|
||||||
|
|
@ -317,12 +318,30 @@ class RMDFilingHandler(BaseServiceHandler):
|
||||||
work_dir,
|
work_dir,
|
||||||
f"robocall_mitigation_program_{order_number}_{date_str}.docx",
|
f"robocall_mitigation_program_{order_number}_{date_str}.docx",
|
||||||
)
|
)
|
||||||
|
# Map stir_shaken_status to Exhibit A rmd_option
|
||||||
|
_STATUS_TO_OPTION = {
|
||||||
|
"complete_implementation": "option1",
|
||||||
|
"partial_implementation": "option2",
|
||||||
|
"terminate_only": "terminate_only",
|
||||||
|
"robocall_mitigation_only": "option3",
|
||||||
|
"exempt_small_carrier": "option3",
|
||||||
|
}
|
||||||
exhibit = generate_exhibit_a(
|
exhibit = generate_exhibit_a(
|
||||||
entity_name=entity.get("legal_name", ""),
|
entity_name=entity.get("legal_name", ""),
|
||||||
frn=entity.get("frn", ""),
|
frn=entity.get("frn", ""),
|
||||||
|
ocn=entity.get("ocn", ""),
|
||||||
carrier_role=role,
|
carrier_role=role,
|
||||||
carrier_metadata=entity.get("carrier_metadata", {}),
|
carrier_metadata=entity.get("carrier_metadata", {}),
|
||||||
upstream_provider_name=entity.get("upstream_provider_name", ""),
|
upstream_provider_name=entity.get("upstream_provider_name", ""),
|
||||||
|
is_wholesale=entity.get("is_wholesale", False),
|
||||||
|
is_gateway=entity.get("is_gateway_provider", False),
|
||||||
|
foreign_traffic=entity.get("is_international_only", False),
|
||||||
|
rmd_option=_STATUS_TO_OPTION.get(stir_status, "option2"),
|
||||||
|
contact_name=entity.get("contact_name", ""),
|
||||||
|
contact_title=entity.get("contact_title", ""),
|
||||||
|
contact_email=entity.get("contact_email", ""),
|
||||||
|
contact_phone=entity.get("contact_phone", ""),
|
||||||
|
address=entity.get("address", ""),
|
||||||
llm_generate=self._call_llm,
|
llm_generate=self._call_llm,
|
||||||
output_path=exhibit_docx,
|
output_path=exhibit_docx,
|
||||||
)
|
)
|
||||||
|
|
@ -422,8 +441,11 @@ class RMDFilingHandler(BaseServiceHandler):
|
||||||
)
|
)
|
||||||
|
|
||||||
# STIR/SHAKEN implementation status — value must match the
|
# STIR/SHAKEN implementation status — value must match the
|
||||||
# RMD form radio options.
|
# RMD form radio options. terminate_only maps to
|
||||||
|
# partial_implementation on the FCC form (verification-only).
|
||||||
stir_status = entity.get("stir_shaken_status", "complete_implementation")
|
stir_status = entity.get("stir_shaken_status", "complete_implementation")
|
||||||
|
if stir_status == "terminate_only":
|
||||||
|
stir_status = "partial_implementation"
|
||||||
await page.click(f'input[name="stir_shaken_status"][value="{stir_status}"]')
|
await page.click(f'input[name="stir_shaken_status"][value="{stir_status}"]')
|
||||||
await human_delay()
|
await human_delay()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,16 +13,18 @@
|
||||||
<label class="pw-field">STIR/SHAKEN implementation</label>
|
<label class="pw-field">STIR/SHAKEN implementation</label>
|
||||||
<select id="pw-ss-status" class="pw-input">
|
<select id="pw-ss-status" class="pw-input">
|
||||||
<option value="">Select…</option>
|
<option value="">Select…</option>
|
||||||
<option value="complete_implementation">Complete — I sign calls with my own cert</option>
|
<option value="complete_implementation">Complete — I sign all outbound calls with my own STI certificate</option>
|
||||||
<option value="partial_implementation">Partial — upstream carrier signs my traffic</option>
|
<option value="partial_implementation">Partial — my upstream carrier signs calls on my behalf</option>
|
||||||
|
<option value="terminate_only">Terminate only — I only receive pre-signed calls, I don't originate</option>
|
||||||
<option value="robocall_mitigation_only">Robocall Mitigation Only — no STIR/SHAKEN signing</option>
|
<option value="robocall_mitigation_only">Robocall Mitigation Only — no STIR/SHAKEN signing</option>
|
||||||
<option value="exempt_small_carrier">Exempt small carrier</option>
|
<option value="exempt_small_carrier">Exempt small carrier</option>
|
||||||
</select>
|
</select>
|
||||||
|
<p id="pw-ss-hint" class="pw-hint" hidden></p>
|
||||||
|
|
||||||
<label class="pw-field">STI-CA vendor preference (optional)</label>
|
<label class="pw-field" id="pw-ss-vendor-label">STI-CA vendor preference (optional)</label>
|
||||||
<input type="text" id="pw-ss-vendor" class="pw-input" placeholder="e.g. iconectiv, Peeringhub, Neustar (leave blank if unsure)" />
|
<input type="text" id="pw-ss-vendor" class="pw-input" placeholder="e.g. iconectiv, Peeringhub, Neustar (leave blank if unsure)" />
|
||||||
|
|
||||||
<label class="pw-field">Upstream voice provider (if partial / robocall-mitigation-only)</label>
|
<label class="pw-field" id="pw-ss-upstream-label">Upstream voice provider (if partial / robocall-mitigation-only)</label>
|
||||||
<input type="text" id="pw-ss-upstream" class="pw-input" placeholder="e.g. Bandwidth.com, VoIP Innovations" />
|
<input type="text" id="pw-ss-upstream" class="pw-input" placeholder="e.g. Bandwidth.com, VoIP Innovations" />
|
||||||
|
|
||||||
<div id="pw-ss-err" class="pw-err" hidden></div>
|
<div id="pw-ss-err" class="pw-err" hidden></div>
|
||||||
|
|
@ -33,17 +35,49 @@
|
||||||
.pw-help { color: #64748b; font-size: 0.9rem; margin-bottom: 1rem; }
|
.pw-help { color: #64748b; font-size: 0.9rem; margin-bottom: 1rem; }
|
||||||
.pw-field { display: block; font-weight: 600; margin: 0.8rem 0 0.2rem; font-size: 0.88rem; }
|
.pw-field { display: block; font-weight: 600; margin: 0.8rem 0 0.2rem; font-size: 0.88rem; }
|
||||||
.pw-input { width: 100%; padding: 0.5rem 0.7rem; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 0.93rem; }
|
.pw-input { width: 100%; padding: 0.5rem 0.7rem; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 0.93rem; }
|
||||||
|
.pw-hint { color: #4b5563; font-size: 0.82rem; margin-top: 0.3rem; padding: 0.5rem 0.7rem; background: #f0f4f8; border-radius: 6px; line-height: 1.5; }
|
||||||
.pw-err { color: #b91c1c; margin-top: 0.75rem; font-size: 0.9rem; }
|
.pw-err { color: #b91c1c; margin-top: 0.75rem; font-size: 0.9rem; }
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const g = <T extends HTMLElement>(id: string) => document.getElementById(id) as T;
|
const g = <T extends HTMLElement>(id: string) => document.getElementById(id) as T;
|
||||||
|
const HINTS: Record<string, string> = {
|
||||||
|
complete_implementation: "You originate calls and sign them with your own STI certificate from an approved Certificate Authority.",
|
||||||
|
partial_implementation: "Your upstream carrier signs calls on your behalf. As of June 2025, you must still make all attestation-level decisions — your upstream only performs the technical act of signing.",
|
||||||
|
terminate_only: "You only receive and terminate pre-signed calls from other carriers. You verify incoming signatures (software config) but don't need your own signing certificate. Your RMD will be filed as \"partial implementation\" describing your verification process.",
|
||||||
|
robocall_mitigation_only: "You have a robocall mitigation program but no STIR/SHAKEN signing. This is typically for non-IP networks or providers awaiting STI certificate issuance.",
|
||||||
|
exempt_small_carrier: "Small carriers with fewer than 100,000 subscriber lines may qualify for a temporary exemption.",
|
||||||
|
};
|
||||||
|
|
||||||
|
function updateSSFields() {
|
||||||
|
const val = g<HTMLSelectElement>("pw-ss-status").value;
|
||||||
|
const hint = g<HTMLElement>("pw-ss-hint");
|
||||||
|
const vendorLabel = g<HTMLElement>("pw-ss-vendor-label");
|
||||||
|
const vendorInput = g<HTMLInputElement>("pw-ss-vendor");
|
||||||
|
const upstreamLabel = g<HTMLElement>("pw-ss-upstream-label");
|
||||||
|
const upstreamInput = g<HTMLInputElement>("pw-ss-upstream");
|
||||||
|
// Show hint
|
||||||
|
if (val && HINTS[val]) { hint.textContent = HINTS[val]; hint.hidden = false; }
|
||||||
|
else { hint.hidden = true; }
|
||||||
|
// Show/hide vendor (only for complete)
|
||||||
|
const showVendor = val === "complete_implementation";
|
||||||
|
vendorLabel.style.display = showVendor ? "" : "none";
|
||||||
|
vendorInput.style.display = showVendor ? "" : "none";
|
||||||
|
// Show/hide upstream (for partial and robocall-only)
|
||||||
|
const showUpstream = val === "partial_implementation" || val === "robocall_mitigation_only";
|
||||||
|
upstreamLabel.style.display = showUpstream ? "" : "none";
|
||||||
|
upstreamInput.style.display = showUpstream ? "" : "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
g<HTMLSelectElement>("pw-ss-status").addEventListener("change", updateSSFields);
|
||||||
|
|
||||||
window.addEventListener("pw:step-shown", (evt: any) => {
|
window.addEventListener("pw:step-shown", (evt: any) => {
|
||||||
if (evt.detail.step !== "stir_shaken") return;
|
if (evt.detail.step !== "stir_shaken") return;
|
||||||
const s = (window as any).PWIntake.get();
|
const s = (window as any).PWIntake.get();
|
||||||
g<HTMLSelectElement>("pw-ss-status").value = s.intake_data?.target_stir_shaken_status || s.entity?.stir_shaken_status || "";
|
g<HTMLSelectElement>("pw-ss-status").value = s.intake_data?.target_stir_shaken_status || s.entity?.stir_shaken_status || "";
|
||||||
g<HTMLInputElement>("pw-ss-vendor").value = s.intake_data?.sti_ca_vendor || "";
|
g<HTMLInputElement>("pw-ss-vendor").value = s.intake_data?.sti_ca_vendor || "";
|
||||||
g<HTMLInputElement>("pw-ss-upstream").value = s.intake_data?.upstream_provider_name || "";
|
g<HTMLInputElement>("pw-ss-upstream").value = s.intake_data?.upstream_provider_name || "";
|
||||||
|
updateSSFields();
|
||||||
});
|
});
|
||||||
window.addEventListener("pw:step-next", (evt: any) => {
|
window.addEventListener("pw:step-next", (evt: any) => {
|
||||||
const PW = (window as any).PWIntake;
|
const PW = (window as any).PWIntake;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue