Add dependency-review caller workflow (#3)
Some checks are pending
Deploy / deploy (push) Waiting to run

* Add dependency-review caller workflow

Add a pull_request-triggered caller that invokes the org-level
callable-dependency-review workflow to scan dependency changes and
fail on high-severity advisories.

* chore: retrigger checks

* chore: retrigger dep review (post-fix)

* style: ruff format src
This commit is contained in:
Adam Moussa 2026-06-05 12:34:16 -04:00 • committed by GitHub
parent 899f07d09c
commit b7c65cedfb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 150 additions and 55 deletions

View file

@ -0,0 +1,6 @@
name: Dependency Review
on:
pull_request:
jobs:
review:
uses: Sea-Haven-Industries/.github/.github/workflows/callable-dependency-review.yaml@main

View file

@ -53,6 +53,7 @@ def _get_slack_token():
# Front API
# ---------------------------------------------------------------------------
def _front_get(url_or_path, params=None):
if url_or_path.startswith("http"):
url = url_or_path
@ -61,10 +62,13 @@ def _front_get(url_or_path, params=None):
if params:
url += "?" + parse.urlencode(params, doseq=True)
req = request.Request(url, headers={
"Authorization": f"Bearer {_get_front_token()}",
"Accept": "application/json",
})
req = request.Request(
url,
headers={
"Authorization": f"Bearer {_get_front_token()}",
"Accept": "application/json",
},
)
time.sleep(RATE_LIMIT_DELAY)
@ -97,12 +101,17 @@ def _front_paginate(path, params=None):
# Slack API
# ---------------------------------------------------------------------------
def _slack_post(method, payload):
data = json.dumps(payload).encode()
req = request.Request(f"{SLACK_BASE}/{method}", data=data, headers={
"Content-Type": "application/json; charset=utf-8",
"Authorization": f"Bearer {_get_slack_token()}",
})
req = request.Request(
f"{SLACK_BASE}/{method}",
data=data,
headers={
"Content-Type": "application/json; charset=utf-8",
"Authorization": f"Bearer {_get_slack_token()}",
},
)
try:
with request.urlopen(req) as resp:
@ -117,9 +126,12 @@ def _slack_post(method, payload):
def _slack_get(method, params):
url = f"{SLACK_BASE}/{method}?" + parse.urlencode(params)
req = request.Request(url, headers={
"Authorization": f"Bearer {_get_slack_token()}",
})
req = request.Request(
url,
headers={
"Authorization": f"Bearer {_get_slack_token()}",
},
)
try:
with request.urlopen(req) as resp:
@ -146,17 +158,21 @@ def _resolve_slack_user(email):
def _send_slack(channel, blocks, text):
_slack_post("chat.postMessage", {
"channel": channel,
"blocks": blocks,
"text": text,
})
_slack_post(
"chat.postMessage",
{
"channel": channel,
"blocks": blocks,
"text": text,
},
)
# ---------------------------------------------------------------------------
# Business time calculation (weekdays only, Eastern time)
# ---------------------------------------------------------------------------
def _business_minutes_elapsed(since_utc, now_utc):
since = since_utc.astimezone(EASTERN)
now = now_utc.astimezone(EASTERN)
@ -186,6 +202,7 @@ def _business_minutes_elapsed(since_utc, now_utc):
# DynamoDB dedup
# ---------------------------------------------------------------------------
def _already_alerted(conv_id, tier):
resp = _get_table().get_item(Key={"conversationId": conv_id})
item = resp.get("Item")
@ -229,6 +246,7 @@ def _record_run(today_str):
# Slack message blocks
# ---------------------------------------------------------------------------
def _tier1_blocks(conv, assignee_email=None):
subject = conv.get("subject", "No subject")
conv_id = conv.get("id", "")
@ -238,13 +256,25 @@ def _tier1_blocks(conv, assignee_email=None):
status = f"Assigned to {assignee_email}" if assignee_email else "Unassigned"
return [
{"type": "header", "text": {"type": "plain_text", "text": ":warning: SLA Breach: 1-Hour Acknowledgment"}},
{"type": "section", "text": {"type": "mrkdwn", "text": (
f"*<{link}|{subject}>*\n"
f"Last inbound: {ts}\n"
f"Status: {status}\n"
f"_No reply for over 1 business hour._"
)}},
{
"type": "header",
"text": {
"type": "plain_text",
"text": ":warning: SLA Breach: 1-Hour Acknowledgment",
},
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": (
f"*<{link}|{subject}>*\n"
f"Last inbound: {ts}\n"
f"Status: {status}\n"
f"_No reply for over 1 business hour._"
),
},
},
]
@ -258,13 +288,25 @@ def _tier2_blocks(conv):
status = f"Assigned to {assignee['email']}" if assignee else "Unassigned"
return [
{"type": "header", "text": {"type": "plain_text", "text": ":rotating_light: SLA Breach: 1-Day Action Required"}},
{"type": "section", "text": {"type": "mrkdwn", "text": (
f"*<{link}|{subject}>*\n"
f"Last inbound: {ts}\n"
f"Status: {status}\n"
f"_No reply for over 1 business day. Immediate attention required._"
)}},
{
"type": "header",
"text": {
"type": "plain_text",
"text": ":rotating_light: SLA Breach: 1-Day Action Required",
},
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": (
f"*<{link}|{subject}>*\n"
f"Last inbound: {ts}\n"
f"Status: {status}\n"
f"_No reply for over 1 business day. Immediate attention required._"
),
},
},
]
@ -291,7 +333,13 @@ def _summary_blocks(tier1_breaches, tier2_breaches):
total = len(tier1_breaches) + len(tier2_breaches)
return [
{"type": "header", "text": {"type": "plain_text", "text": f":sunrise: Morning SLA Summary — {total} breach{'es' if total != 1 else ''}"}},
{
"type": "header",
"text": {
"type": "plain_text",
"text": f":sunrise: Morning SLA Summary — {total} breach{'es' if total != 1 else ''}",
},
},
{"type": "section", "text": {"type": "mrkdwn", "text": "\n".join(lines)}},
]
@ -300,6 +348,7 @@ def _summary_blocks(tier1_breaches, tier2_breaches):
# Handler
# ---------------------------------------------------------------------------
def handler(event, context):
ack_threshold = int(os.environ["ACK_SLA_MINUTES"])
action_threshold = int(os.environ["ACTION_SLA_MINUTES"])
@ -317,7 +366,9 @@ def handler(event, context):
return {"skipped": True, "reason": "before_start_date"}
if now_et.hour < BH_START or now_et.hour >= BH_END:
logger.info("Outside business hours (%s ET), skipping", now_et.strftime("%H:%M"))
logger.info(
"Outside business hours (%s ET), skipping", now_et.strftime("%H:%M")
)
return {"skipped": True, "reason": "outside_business_hours"}
today_str = now_et.strftime("%Y-%m-%d")
@ -336,8 +387,11 @@ def handler(event, context):
if monitor_set:
shared = [i for i in shared if i.get("name", "").lower() in monitor_set]
logger.info("Monitoring %d inboxes: %s", len(shared),
", ".join(i.get("name", "?") for i in shared))
logger.info(
"Monitoring %d inboxes: %s",
len(shared),
", ".join(i.get("name", "?") for i in shared),
)
tier1_breaches = []
tier2_breaches = []
@ -356,7 +410,9 @@ def handler(event, context):
logger.error("Failed to fetch conversations for %s: %s", inbox_name, e)
continue
logger.info("Inbox '%s': %d recent open conversations", inbox_name, len(conversations))
logger.info(
"Inbox '%s': %d recent open conversations", inbox_name, len(conversations)
)
for conv in conversations:
if conv.get("id") in seen:
@ -377,12 +433,18 @@ def handler(event, context):
logger.info("Tier 2 breach: %s", conv_id)
elif elapsed >= ack_threshold and not _already_alerted(conv_id, 1):
email = assignee["email"] if assignee and assignee.get("email") else None
email = (
assignee["email"]
if assignee and assignee.get("email")
else None
)
tier1_breaches.append((conv, email))
logger.info("Tier 1 breach: %s", conv_id)
except Exception:
logger.exception("Error processing conversation %s", conv.get("id", "?"))
logger.exception(
"Error processing conversation %s", conv.get("id", "?")
)
total = len(tier1_breaches) + len(tier2_breaches)
@ -398,18 +460,27 @@ def handler(event, context):
for conv in tier2_breaches:
adam_uid = _resolve_slack_user(adam_email)
target = adam_uid or alert_channel
_send_slack(target, _tier2_blocks(conv),
f"SLA Breach: {conv.get('subject', '')} - 1 day without reply")
_send_slack(
target,
_tier2_blocks(conv),
f"SLA Breach: {conv.get('subject', '')} - 1 day without reply",
)
for conv, email in tier1_breaches:
if email:
uid = _resolve_slack_user(email)
target = uid or alert_channel
_send_slack(target, _tier1_blocks(conv, email),
f"SLA Breach: {conv.get('subject', '')} - 1 hour without reply")
_send_slack(
target,
_tier1_blocks(conv, email),
f"SLA Breach: {conv.get('subject', '')} - 1 hour without reply",
)
else:
_send_slack(alert_channel, _tier1_blocks(conv),
f"SLA Breach: {conv.get('subject', '')} - unassigned, 1 hour without reply")
_send_slack(
alert_channel,
_tier1_blocks(conv),
f"SLA Breach: {conv.get('subject', '')} - unassigned, 1 hour without reply",
)
for conv in tier2_breaches:
_record_alert(conv["id"], 2)

View file

@ -52,14 +52,18 @@ def _list_google_users(org_units):
page_token = None
while True:
result = service.users().list(
customer="my_customer",
maxResults=500,
projection="full",
orderBy="email",
query=f"orgUnitPath='{ou}'",
pageToken=page_token,
).execute()
result = (
service.users()
.list(
customer="my_customer",
maxResults=500,
projection="full",
orderBy="email",
query=f"orgUnitPath='{ou}'",
pageToken=page_token,
)
.execute()
)
for user in result.get("users", []):
email = user.get("primaryEmail", "")
@ -71,7 +75,11 @@ def _list_google_users(org_units):
if not page_token:
break
logger.info(" Found %d users in %s", len([u for u in all_users if u.get("primaryEmail") in seen]), ou)
logger.info(
" Found %d users in %s",
len([u for u in all_users if u.get("primaryEmail") in seen]),
ou,
)
return all_users
@ -88,8 +96,12 @@ def _extract_user_fields(google_user):
phone = ""
phones = google_user.get("phones", [])
if phones:
work_phone = next((p for p in phones if p.get("type") == "work" and p.get("value")), None)
primary_phone = next((p for p in phones if p.get("primary") and p.get("value")), None)
work_phone = next(
(p for p in phones if p.get("type") == "work" and p.get("value")), None
)
primary_phone = next(
(p for p in phones if p.get("primary") and p.get("value")), None
)
fallback = next((p for p in phones if p.get("value")), None)
phone = (work_phone or primary_phone or fallback or {}).get("value", "")
@ -147,7 +159,13 @@ def handler(event, context):
google_users = _list_google_users(org_units)
logger.info("Found %d total users across %d OUs", len(google_users), len(org_units))
summary = {"updated": 0, "not_in_front": 0, "no_data": 0, "errors": 0, "error_details": []}
summary = {
"updated": 0,
"not_in_front": 0,
"no_data": 0,
"errors": 0,
"error_details": [],
}
for user in google_users:
email, job_title, phone = _extract_user_fields(user)