2026-04-03 18:32:32 -04:00
|
|
|
AWSTemplateFormatVersion: "2010-09-09"
|
|
|
|
|
Transform: AWS::Serverless-2016-10-31
|
|
|
|
|
Description: After-Hours Shift Manager — Slack bot for managing on-call shifts with 3CX integration
|
|
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
|
Timezone:
|
|
|
|
|
Type: String
|
|
|
|
|
Default: "America/New_York"
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
ShiftChannel:
|
2026-04-03 18:32:32 -04:00
|
|
|
Type: String
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
Description: Slack channel ID for schedule posts and shift notifications
|
|
|
|
|
QueueNumber:
|
|
|
|
|
Type: String
|
|
|
|
|
Default: "801"
|
|
|
|
|
Description: 3CX queue extension number to update
|
2026-04-03 18:32:32 -04:00
|
|
|
|
|
|
|
|
Globals:
|
|
|
|
|
Function:
|
|
|
|
|
Runtime: python3.12
|
|
|
|
|
Timeout: 30
|
|
|
|
|
MemorySize: 1024
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
Architectures:
|
|
|
|
|
- arm64
|
2026-06-10 14:14:46 -04:00
|
|
|
PermissionsBoundary: arn:aws:iam::328440206208:policy/seahaven-lambda-execution-boundary
|
2026-06-05 17:47:41 -04:00
|
|
|
# Access logging + default throttling on the implicit HTTP API (audit M-18).
|
|
|
|
|
HttpApi:
|
|
|
|
|
AccessLogSettings:
|
|
|
|
|
DestinationArn: !GetAtt ApiAccessLogGroup.Arn
|
|
|
|
|
Format: '{"requestId":"$context.requestId","ip":"$context.identity.sourceIp","requestTime":"$context.requestTime","method":"$context.httpMethod","routeKey":"$context.routeKey","status":"$context.status","protocol":"$context.protocol","responseLength":"$context.responseLength","integrationError":"$context.integrationErrorMessage"}'
|
|
|
|
|
DefaultRouteSettings:
|
|
|
|
|
ThrottlingBurstLimit: 50
|
|
|
|
|
ThrottlingRateLimit: 100
|
2026-04-03 18:32:32 -04:00
|
|
|
|
|
|
|
|
Resources:
|
2026-06-05 17:47:41 -04:00
|
|
|
ApiAccessLogGroup:
|
|
|
|
|
Type: AWS::Logs::LogGroup
|
|
|
|
|
Properties:
|
|
|
|
|
LogGroupName: /aws/apigateway/afterhours-shift-manager
|
|
|
|
|
RetentionInDays: 90
|
|
|
|
|
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
# --- Shared Lambda Layer ---
|
|
|
|
|
SharedLayer:
|
|
|
|
|
Type: AWS::Serverless::LayerVersion
|
|
|
|
|
Properties:
|
|
|
|
|
LayerName: afterhours-shared
|
|
|
|
|
ContentUri: src/shared/
|
|
|
|
|
CompatibleRuntimes:
|
|
|
|
|
- python3.12
|
|
|
|
|
CompatibleArchitectures:
|
|
|
|
|
- arm64
|
|
|
|
|
Metadata:
|
|
|
|
|
BuildMethod: python3.12
|
|
|
|
|
BuildArchitecture: arm64
|
|
|
|
|
|
2026-04-03 18:32:32 -04:00
|
|
|
# --- DynamoDB ---
|
|
|
|
|
ShiftTable:
|
|
|
|
|
Type: AWS::DynamoDB::Table
|
|
|
|
|
Properties:
|
|
|
|
|
TableName: afterhours-shifts
|
|
|
|
|
BillingMode: PAY_PER_REQUEST
|
|
|
|
|
AttributeDefinitions:
|
|
|
|
|
- AttributeName: PK
|
|
|
|
|
AttributeType: S
|
|
|
|
|
- AttributeName: SK
|
|
|
|
|
AttributeType: S
|
|
|
|
|
KeySchema:
|
|
|
|
|
- AttributeName: PK
|
|
|
|
|
KeyType: HASH
|
|
|
|
|
- AttributeName: SK
|
|
|
|
|
KeyType: RANGE
|
2026-06-01 19:22:55 -04:00
|
|
|
TimeToLiveSpecification:
|
|
|
|
|
AttributeName: expires_at
|
|
|
|
|
Enabled: true
|
2026-04-03 18:32:32 -04:00
|
|
|
|
|
|
|
|
# --- Slack Bot Lambda ---
|
|
|
|
|
SlackBotFunction:
|
|
|
|
|
Type: AWS::Serverless::Function
|
|
|
|
|
Properties:
|
|
|
|
|
FunctionName: afterhours-shift-manager
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
Handler: handler.handler
|
|
|
|
|
CodeUri: src/slack-bot/
|
|
|
|
|
Layers:
|
|
|
|
|
- !Ref SharedLayer
|
2026-04-03 18:32:32 -04:00
|
|
|
Environment:
|
|
|
|
|
Variables:
|
|
|
|
|
SHIFT_TABLE: !Ref ShiftTable
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
SLACK_BOT_TOKEN_SECRET: afterhours-shift-manager/slack-bot-token
|
|
|
|
|
SLACK_SIGNING_SECRET: afterhours-shift-manager/slack-signing-secret
|
|
|
|
|
SHIFT_CHANNEL: !Ref ShiftChannel
|
|
|
|
|
TCX_SECRET_PREFIX: afterhours-shift-manager/3cx-
|
|
|
|
|
QUEUE_NUMBER: !Ref QueueNumber
|
2026-04-03 18:32:32 -04:00
|
|
|
TZ: !Ref Timezone
|
2026-06-17 11:14:29 -04:00
|
|
|
# Holiday scheduling: per-holiday one-off schedules target the router,
|
|
|
|
|
# passing the scheduler exec role; inline activation invokes it directly.
|
|
|
|
|
HOLIDAY_ROUTER_ARN: !GetAtt HolidayRouterFunction.Arn
|
|
|
|
|
HOLIDAY_SCHEDULER_ROLE_ARN: !GetAtt HolidaySchedulerExecutionRole.Arn
|
2026-04-03 18:32:32 -04:00
|
|
|
Policies:
|
|
|
|
|
- DynamoDBCrudPolicy:
|
|
|
|
|
TableName: !Ref ShiftTable
|
|
|
|
|
- Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
- secretsmanager:GetSecretValue
|
2026-04-03 18:32:32 -04:00
|
|
|
Resource:
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
- !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:afterhours-shift-manager/*"
|
2026-06-17 11:14:29 -04:00
|
|
|
# Manage the per-holiday EventBridge Scheduler one-off schedules
|
|
|
|
|
# (08:00 activate / 17:00 deactivate of the holiday router).
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
|
|
|
|
- scheduler:CreateSchedule
|
|
|
|
|
- scheduler:DeleteSchedule
|
|
|
|
|
- scheduler:GetSchedule
|
|
|
|
|
# Predictable names (holiday-activate-/holiday-deactivate-<date>)
|
|
|
|
|
# in the default group — scope to those rather than all schedules.
|
|
|
|
|
Resource:
|
|
|
|
|
- !Sub "arn:aws:scheduler:${AWS::Region}:${AWS::AccountId}:schedule/default/holiday-*"
|
|
|
|
|
# PassRole only for the holiday scheduler exec role, and only when
|
|
|
|
|
# handed to EventBridge Scheduler.
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action: iam:PassRole
|
|
|
|
|
Resource: !GetAtt HolidaySchedulerExecutionRole.Arn
|
|
|
|
|
Condition:
|
|
|
|
|
StringEquals:
|
|
|
|
|
iam:PassedToService: scheduler.amazonaws.com
|
|
|
|
|
# Inline activation (holiday added mid-window) invokes the router now.
|
|
|
|
|
# Unqualified ARN only — we invoke the base function, no alias/version.
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action: lambda:InvokeFunction
|
|
|
|
|
Resource: !GetAtt HolidayRouterFunction.Arn
|
2026-04-03 18:32:32 -04:00
|
|
|
Events:
|
|
|
|
|
SlackEvents:
|
|
|
|
|
Type: HttpApi
|
|
|
|
|
Properties:
|
|
|
|
|
Path: /slack/events
|
|
|
|
|
Method: POST
|
|
|
|
|
|
|
|
|
|
# --- Weekly Schedule Post (Monday 7am ET) ---
|
|
|
|
|
WeeklyPostFunction:
|
|
|
|
|
Type: AWS::Serverless::Function
|
|
|
|
|
Properties:
|
|
|
|
|
FunctionName: afterhours-weekly-post
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
Handler: app.handler
|
|
|
|
|
CodeUri: src/weekly-post/
|
|
|
|
|
Layers:
|
|
|
|
|
- !Ref SharedLayer
|
2026-04-03 18:32:32 -04:00
|
|
|
Environment:
|
|
|
|
|
Variables:
|
|
|
|
|
SHIFT_TABLE: !Ref ShiftTable
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
SLACK_BOT_TOKEN_SECRET: afterhours-shift-manager/slack-bot-token
|
|
|
|
|
SHIFT_CHANNEL: !Ref ShiftChannel
|
2026-04-07 18:54:19 -04:00
|
|
|
SES_SENDER: noreply@seahaven.com
|
2026-04-08 13:24:45 -04:00
|
|
|
PAYROLL_RECIPIENTS: payroll@seahaven.com
|
2026-04-07 19:07:44 -04:00
|
|
|
PAY_REPORT_USER: U0A3SC48T47
|
2026-04-03 18:32:32 -04:00
|
|
|
TZ: !Ref Timezone
|
|
|
|
|
Policies:
|
2026-04-07 18:54:19 -04:00
|
|
|
- DynamoDBCrudPolicy:
|
2026-04-03 18:32:32 -04:00
|
|
|
TableName: !Ref ShiftTable
|
|
|
|
|
- Statement:
|
2026-06-18 12:05:25 -04:00
|
|
|
# Least privilege: only the Slack bot token, not the whole namespace.
|
2026-04-03 18:32:32 -04:00
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
- secretsmanager:GetSecretValue
|
2026-04-03 18:32:32 -04:00
|
|
|
Resource:
|
2026-06-18 12:05:25 -04:00
|
|
|
- !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:afterhours-shift-manager/slack-bot-token-*"
|
2026-04-07 18:54:19 -04:00
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
|
|
|
|
- ses:SendEmail
|
|
|
|
|
Resource:
|
2026-06-18 12:05:25 -04:00
|
|
|
# Only the single sending identity (noreply@seahaven.com), not
|
|
|
|
|
# every identity in the account.
|
|
|
|
|
- !Sub "arn:aws:ses:${AWS::Region}:${AWS::AccountId}:identity/noreply@seahaven.com"
|
2026-06-15 13:24:32 -04:00
|
|
|
# The sending identity has a default configuration set
|
|
|
|
|
# (seahaven-email-events); SES authorizes SendEmail against the
|
|
|
|
|
# config-set resource too, so it must be granted alongside the
|
|
|
|
|
# identity or the send is denied. Scoped to the known set name.
|
|
|
|
|
- !Sub "arn:aws:ses:${AWS::Region}:${AWS::AccountId}:configuration-set/seahaven-email-events"
|
2026-04-03 18:32:32 -04:00
|
|
|
Events:
|
|
|
|
|
# EST: 7am ET = 12:00 UTC (Nov-Mar)
|
|
|
|
|
WeeklyPostEST:
|
|
|
|
|
Type: Schedule
|
|
|
|
|
Properties:
|
|
|
|
|
Schedule: cron(0 12 ? * MON *)
|
|
|
|
|
Description: "Post weekly schedule Monday 7am EST"
|
|
|
|
|
Enabled: true
|
|
|
|
|
# EDT: 7am ET = 11:00 UTC (Mar-Nov)
|
|
|
|
|
WeeklyPostEDT:
|
|
|
|
|
Type: Schedule
|
|
|
|
|
Properties:
|
|
|
|
|
Schedule: cron(0 11 ? * MON *)
|
|
|
|
|
Description: "Post weekly schedule Monday 7am EDT"
|
|
|
|
|
Enabled: true
|
|
|
|
|
|
2026-04-07 17:52:43 -04:00
|
|
|
# --- Roster Sync Lambda (daily sync from 3CX) ---
|
|
|
|
|
RosterSyncFunction:
|
|
|
|
|
Type: AWS::Serverless::Function
|
|
|
|
|
Properties:
|
|
|
|
|
FunctionName: afterhours-roster-sync
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
Handler: app.handler
|
|
|
|
|
CodeUri: src/roster-sync/
|
|
|
|
|
Layers:
|
|
|
|
|
- !Ref SharedLayer
|
2026-04-07 17:52:43 -04:00
|
|
|
Timeout: 60
|
|
|
|
|
Environment:
|
|
|
|
|
Variables:
|
|
|
|
|
SHIFT_TABLE: !Ref ShiftTable
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
TCX_SECRET_PREFIX: afterhours-shift-manager/3cx-
|
2026-04-07 17:52:43 -04:00
|
|
|
SYNC_GROUP: DEFAULT
|
|
|
|
|
TZ: !Ref Timezone
|
|
|
|
|
Policies:
|
|
|
|
|
- DynamoDBCrudPolicy:
|
|
|
|
|
TableName: !Ref ShiftTable
|
|
|
|
|
- Statement:
|
2026-06-18 12:05:25 -04:00
|
|
|
# Least privilege: only the 3cx-* secrets this function reads.
|
2026-04-07 17:52:43 -04:00
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
- secretsmanager:GetSecretValue
|
2026-04-07 17:52:43 -04:00
|
|
|
Resource:
|
2026-06-18 12:05:25 -04:00
|
|
|
- !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:afterhours-shift-manager/3cx-*"
|
2026-04-07 17:52:43 -04:00
|
|
|
Events:
|
|
|
|
|
# Daily at 6am ET (before the 7am schedule post and 8am 3CX scheduler)
|
|
|
|
|
# EST: 6am ET = 11:00 UTC (Nov-Mar)
|
|
|
|
|
RosterSyncEST:
|
|
|
|
|
Type: Schedule
|
|
|
|
|
Properties:
|
|
|
|
|
Schedule: cron(0 11 ? * * *)
|
|
|
|
|
Description: "Sync roster from 3CX at 6am EST"
|
|
|
|
|
Enabled: true
|
|
|
|
|
# EDT: 6am ET = 10:00 UTC (Mar-Nov)
|
|
|
|
|
RosterSyncEDT:
|
|
|
|
|
Type: Schedule
|
|
|
|
|
Properties:
|
|
|
|
|
Schedule: cron(0 10 ? * * *)
|
|
|
|
|
Description: "Sync roster from 3CX at 6am EDT"
|
|
|
|
|
Enabled: true
|
|
|
|
|
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
# --- Ring Scheduler (daily 3CX queue routing updates) ---
|
|
|
|
|
RingSchedulerFunction:
|
|
|
|
|
Type: AWS::Serverless::Function
|
|
|
|
|
Properties:
|
|
|
|
|
FunctionName: afterhours-ring-scheduler
|
|
|
|
|
Handler: app.handler
|
|
|
|
|
CodeUri: src/ring-scheduler/
|
|
|
|
|
Layers:
|
|
|
|
|
- !Ref SharedLayer
|
|
|
|
|
Timeout: 60
|
|
|
|
|
Environment:
|
|
|
|
|
Variables:
|
|
|
|
|
SHIFT_TABLE: !Ref ShiftTable
|
|
|
|
|
TCX_SECRET_PREFIX: afterhours-shift-manager/3cx-
|
|
|
|
|
QUEUE_NUMBER: !Ref QueueNumber
|
|
|
|
|
TZ: !Ref Timezone
|
|
|
|
|
Policies:
|
2026-06-18 12:05:25 -04:00
|
|
|
- DynamoDBReadPolicy:
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
TableName: !Ref ShiftTable
|
|
|
|
|
- Statement:
|
2026-06-18 12:05:25 -04:00
|
|
|
# Least privilege: only the 3cx-* secrets this function reads.
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
|
|
|
|
- secretsmanager:GetSecretValue
|
|
|
|
|
Resource:
|
2026-06-18 12:05:25 -04:00
|
|
|
- !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:afterhours-shift-manager/3cx-*"
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
Events:
|
|
|
|
|
# Daily at 8am ET — update after-hours routing
|
|
|
|
|
DailyScheduleEST:
|
|
|
|
|
Type: Schedule
|
|
|
|
|
Properties:
|
|
|
|
|
Schedule: cron(0 13 ? * * *)
|
|
|
|
|
Description: "Update 3CX queue at 8am EST"
|
|
|
|
|
Enabled: true
|
|
|
|
|
DailyScheduleEDT:
|
|
|
|
|
Type: Schedule
|
|
|
|
|
Properties:
|
|
|
|
|
Schedule: cron(0 12 ? * * *)
|
|
|
|
|
Description: "Update 3CX queue at 8am EDT"
|
|
|
|
|
Enabled: true
|
|
|
|
|
# Weekends at 5pm ET — switch to night shift person
|
|
|
|
|
WeekendEveningEST:
|
|
|
|
|
Type: Schedule
|
|
|
|
|
Properties:
|
|
|
|
|
Schedule: cron(0 22 ? * SAT,SUN *)
|
|
|
|
|
Description: "Update 3CX queue at 5pm EST weekends"
|
|
|
|
|
Enabled: true
|
|
|
|
|
WeekendEveningEDT:
|
|
|
|
|
Type: Schedule
|
|
|
|
|
Properties:
|
|
|
|
|
Schedule: cron(0 21 ? * SAT,SUN *)
|
|
|
|
|
Description: "Update 3CX queue at 5pm EDT weekends"
|
|
|
|
|
Enabled: true
|
|
|
|
|
|
2026-06-17 12:08:04 -04:00
|
|
|
# Lambda error alarm for the ring scheduler. Mirrors the account-wide
|
|
|
|
|
# operational convention (Lambda-Errors-<fn>, threshold 1 over one 5-min
|
|
|
|
|
# period, Sum, missing=notBreaching) and pages the same site-alerts SNS topic
|
|
|
|
|
# → AWS Chatbot → Slack as the other afterhours-* functions.
|
|
|
|
|
RingSchedulerErrorAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Errors-${RingSchedulerFunction}"
|
|
|
|
|
AlarmDescription: "Ring scheduler Lambda reported one or more errors"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Errors
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref RingSchedulerFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 1
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
2026-06-17 11:14:29 -04:00
|
|
|
# --- Holiday Router (repoints 3CX IVR/queue for a holiday day-shift) ---
|
|
|
|
|
# Invoked with {"action": "activate"|"deactivate", "date": "<YYYY-MM-DD>"} at
|
|
|
|
|
# 08:00 ET (activate) and 17:00 ET (deactivate) for each holiday date. Both
|
|
|
|
|
# operations are idempotent. No standing schedule here — invocation is driven
|
|
|
|
|
# per-holiday-date (the holiday record gates the work; off-days are no-ops).
|
|
|
|
|
HolidayRouterFunction:
|
|
|
|
|
Type: AWS::Serverless::Function
|
|
|
|
|
Properties:
|
|
|
|
|
FunctionName: afterhours-holiday-router
|
|
|
|
|
Handler: app.handler
|
|
|
|
|
CodeUri: src/holiday-router/
|
|
|
|
|
Layers:
|
|
|
|
|
- !Ref SharedLayer
|
|
|
|
|
Timeout: 60
|
|
|
|
|
Environment:
|
|
|
|
|
Variables:
|
|
|
|
|
SHIFT_TABLE: !Ref ShiftTable
|
|
|
|
|
TCX_SECRET_PREFIX: afterhours-shift-manager/3cx-
|
|
|
|
|
TZ: !Ref Timezone
|
|
|
|
|
Policies:
|
|
|
|
|
- DynamoDBCrudPolicy:
|
|
|
|
|
TableName: !Ref ShiftTable
|
|
|
|
|
- Statement:
|
2026-06-18 12:05:25 -04:00
|
|
|
# Least privilege: only the 3cx-* secrets this function reads.
|
2026-06-17 11:14:29 -04:00
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
|
|
|
|
- secretsmanager:GetSecretValue
|
|
|
|
|
Resource:
|
2026-06-18 12:05:25 -04:00
|
|
|
- !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:afterhours-shift-manager/3cx-*"
|
2026-06-17 11:14:29 -04:00
|
|
|
|
|
|
|
|
# Lambda error alarm for the holiday router. Mirrors the account-wide
|
|
|
|
|
# operational convention (Lambda-Errors-<fn>, threshold 1 over one 5-min
|
|
|
|
|
# period, Sum, missing=notBreaching) and pages the same site-alerts SNS topic
|
|
|
|
|
# → AWS Chatbot → Slack as the other afterhours-* functions.
|
|
|
|
|
HolidayRouterErrorAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Errors-${HolidayRouterFunction}"
|
|
|
|
|
AlarmDescription: "Holiday router Lambda reported one or more errors"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Errors
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref HolidayRouterFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 1
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
# EventBridge Scheduler execution role. The slack-bot creates one-off
|
|
|
|
|
# schedules per holiday date (08:00 activate / 17:00 deactivate); Scheduler
|
|
|
|
|
# assumes this role to invoke the holiday router. Auto-named (no RoleName) so
|
|
|
|
|
# the deploy's CAPABILITY_IAM suffices — cd-sam does not pass
|
|
|
|
|
# CAPABILITY_NAMED_IAM. The permissions boundary is REQUIRED: the scoped
|
|
|
|
|
# github-cfn-execution-role gates iam:CreateRole/PutRolePolicy on roles
|
|
|
|
|
# carrying exactly this boundary, so the CI deploy is denied without it.
|
|
|
|
|
HolidaySchedulerExecutionRole:
|
|
|
|
|
Type: AWS::IAM::Role
|
|
|
|
|
Properties:
|
|
|
|
|
PermissionsBoundary: arn:aws:iam::328440206208:policy/seahaven-lambda-execution-boundary
|
|
|
|
|
AssumeRolePolicyDocument:
|
|
|
|
|
Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Principal:
|
|
|
|
|
Service: scheduler.amazonaws.com
|
|
|
|
|
Action: sts:AssumeRole
|
|
|
|
|
Condition:
|
|
|
|
|
StringEquals:
|
|
|
|
|
aws:SourceAccount: !Ref AWS::AccountId
|
|
|
|
|
# Only schedules this stack creates (holiday-* in the default group)
|
|
|
|
|
# may assume the role — not any schedule in the account.
|
|
|
|
|
ArnLike:
|
|
|
|
|
aws:SourceArn: !Sub "arn:aws:scheduler:${AWS::Region}:${AWS::AccountId}:schedule/default/holiday-*"
|
|
|
|
|
Policies:
|
|
|
|
|
- PolicyName: invoke-holiday-router
|
|
|
|
|
PolicyDocument:
|
|
|
|
|
Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action: lambda:InvokeFunction
|
|
|
|
|
Resource: !GetAtt HolidayRouterFunction.Arn
|
|
|
|
|
|
Add changelog-driven releases and App Home tab (#112)
* Add changelog-driven releases and App Home tab
Version the bot continuously from CHANGELOG.md (the single source of
truth for both the version and the staff-readable notes) and surface
changes to users in two ways:
- A new afterhours-release-notifier Lambda posts a "What's New" message
to the shift channel on minor/major releases (patches stay silent).
- The bot gains an App Home "About" tab showing what it does, the
command list, and the current version's notes.
release.yaml runs on Deploy success (not release:published — GITHUB_TOKEN
events don't start downstream workflows), checks out the deployed commit,
and tags + publishes a GitHub Release + invokes the notifier. It assumes a
dedicated, boundary-carrying OIDC role scoped to InvokeFunction on the
notifier; the account's cfn role gates role creation on that boundary.
The manual Version Bump workflow is retired. A CI guard enforces that a
CHANGELOG edit is a clean SemVer bump and that the in-package copy matches.
* Harden release workflow and regex against CodeQL findings
Address three code-scanning alerts on the PR:
- Critical (actions/untrusted-checkout): split release.yaml into a
read-only `prepare` job that checks out and runs repo code, and a
privileged `publish` job (contents:write + OIDC) that never checks out
repo code — it tags, releases, and invokes purely through the GitHub
and AWS APIs. Also assert head_branch == main.
- High x2 (py/polynomial-redos): rewrite the italic and link regexes in
markdown_to_mrkdwn with possessive quantifiers and exclusive character
classes so they run in linear time on adversarial input. Adds a
regression test.
* Move release/announce into Deploy workflow to clear CodeQL
The workflow_run-triggered release.yaml kept tripping CodeQL's
privileged-context rules (untrusted-checkout, then cache-poisoning) —
CodeQL distrusts any workflow_run that checks out a ref, regardless of
the main-only guarantee, and there is no autofix.
Fold the release job into deploy.yaml gated on `needs: deploy`. A
push-to-main run is a trusted context, so checking out and running repo
code with write/OIDC is safe there. This still gates on deploy success
and serializes via the deploy concurrency group, and removes the
separate workflow entirely.
2026-06-11 19:41:31 -04:00
|
|
|
# --- Release Notifier (invoked by release.yaml on minor/major releases) ---
|
|
|
|
|
ReleaseNotifierFunction:
|
|
|
|
|
Type: AWS::Serverless::Function
|
|
|
|
|
Properties:
|
|
|
|
|
FunctionName: afterhours-release-notifier
|
|
|
|
|
Handler: app.handler
|
|
|
|
|
CodeUri: src/release-notifier/
|
|
|
|
|
Layers:
|
|
|
|
|
- !Ref SharedLayer
|
|
|
|
|
Environment:
|
|
|
|
|
Variables:
|
|
|
|
|
SLACK_BOT_TOKEN_SECRET: afterhours-shift-manager/slack-bot-token
|
|
|
|
|
SHIFT_CHANNEL: !Ref ShiftChannel
|
|
|
|
|
TZ: !Ref Timezone
|
|
|
|
|
Policies:
|
|
|
|
|
# Least privilege: only the Slack bot token, not the whole namespace.
|
|
|
|
|
- Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action:
|
|
|
|
|
- secretsmanager:GetSecretValue
|
|
|
|
|
Resource:
|
|
|
|
|
- !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:afterhours-shift-manager/slack-bot-token-*"
|
|
|
|
|
|
|
|
|
|
# GitHub-OIDC role assumed by release.yaml to invoke the notifier. Auto-named
|
|
|
|
|
# (no RoleName) so the deploy's CAPABILITY_IAM is sufficient — cd-sam does not
|
|
|
|
|
# pass CAPABILITY_NAMED_IAM. Trust + permission are scoped to the minimum: this
|
|
|
|
|
# repo's main ref + release workflow, and InvokeFunction on the notifier alone.
|
|
|
|
|
# Its ARN is surfaced as a stack output and set once as the
|
|
|
|
|
# RELEASE_NOTIFY_INVOKE_ROLE_ARN repo variable (see README).
|
|
|
|
|
#
|
|
|
|
|
# The permissions boundary is REQUIRED, not optional: the scoped
|
|
|
|
|
# github-cfn-execution-role's IAM policy gates iam:CreateRole/PutRolePolicy on
|
|
|
|
|
# the role carrying exactly this boundary, so the CI deploy is denied without
|
|
|
|
|
# it. The boundary itself permits lambda:InvokeFunction (Sid LambdaInvoke), so
|
|
|
|
|
# it does not restrict this role's one job.
|
|
|
|
|
ReleaseNotifyInvokeRole:
|
|
|
|
|
Type: AWS::IAM::Role
|
|
|
|
|
Properties:
|
|
|
|
|
PermissionsBoundary: arn:aws:iam::328440206208:policy/seahaven-lambda-execution-boundary
|
|
|
|
|
AssumeRolePolicyDocument:
|
|
|
|
|
Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Principal:
|
|
|
|
|
Federated: !Sub "arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com"
|
|
|
|
|
Action: sts:AssumeRoleWithWebIdentity
|
|
|
|
|
Condition:
|
|
|
|
|
StringEquals:
|
|
|
|
|
token.actions.githubusercontent.com:aud: sts.amazonaws.com
|
|
|
|
|
StringLike:
|
|
|
|
|
token.actions.githubusercontent.com:sub: "repo:Sea-Haven-Industries/afterhours-shift-manager:ref:refs/heads/main"
|
2026-06-12 11:42:55 -04:00
|
|
|
# Defense-in-depth: only the Deploy workflow's release job may assume
|
|
|
|
|
# this role, not any workflow running on main. (The release job lives
|
|
|
|
|
# in deploy.yaml; this must match that workflow's path.)
|
|
|
|
|
token.actions.githubusercontent.com:job_workflow_ref: "Sea-Haven-Industries/afterhours-shift-manager/.github/workflows/deploy.yaml@refs/heads/main"
|
Add changelog-driven releases and App Home tab (#112)
* Add changelog-driven releases and App Home tab
Version the bot continuously from CHANGELOG.md (the single source of
truth for both the version and the staff-readable notes) and surface
changes to users in two ways:
- A new afterhours-release-notifier Lambda posts a "What's New" message
to the shift channel on minor/major releases (patches stay silent).
- The bot gains an App Home "About" tab showing what it does, the
command list, and the current version's notes.
release.yaml runs on Deploy success (not release:published — GITHUB_TOKEN
events don't start downstream workflows), checks out the deployed commit,
and tags + publishes a GitHub Release + invokes the notifier. It assumes a
dedicated, boundary-carrying OIDC role scoped to InvokeFunction on the
notifier; the account's cfn role gates role creation on that boundary.
The manual Version Bump workflow is retired. A CI guard enforces that a
CHANGELOG edit is a clean SemVer bump and that the in-package copy matches.
* Harden release workflow and regex against CodeQL findings
Address three code-scanning alerts on the PR:
- Critical (actions/untrusted-checkout): split release.yaml into a
read-only `prepare` job that checks out and runs repo code, and a
privileged `publish` job (contents:write + OIDC) that never checks out
repo code — it tags, releases, and invokes purely through the GitHub
and AWS APIs. Also assert head_branch == main.
- High x2 (py/polynomial-redos): rewrite the italic and link regexes in
markdown_to_mrkdwn with possessive quantifiers and exclusive character
classes so they run in linear time on adversarial input. Adds a
regression test.
* Move release/announce into Deploy workflow to clear CodeQL
The workflow_run-triggered release.yaml kept tripping CodeQL's
privileged-context rules (untrusted-checkout, then cache-poisoning) —
CodeQL distrusts any workflow_run that checks out a ref, regardless of
the main-only guarantee, and there is no autofix.
Fold the release job into deploy.yaml gated on `needs: deploy`. A
push-to-main run is a trusted context, so checking out and running repo
code with write/OIDC is safe there. This still gates on deploy success
and serializes via the deploy concurrency group, and removes the
separate workflow entirely.
2026-06-11 19:41:31 -04:00
|
|
|
Policies:
|
|
|
|
|
- PolicyName: invoke-release-notifier
|
|
|
|
|
PolicyDocument:
|
|
|
|
|
Version: "2012-10-17"
|
|
|
|
|
Statement:
|
|
|
|
|
- Effect: Allow
|
|
|
|
|
Action: lambda:InvokeFunction
|
|
|
|
|
Resource: !GetAtt ReleaseNotifierFunction.Arn
|
|
|
|
|
|
Add CloudWatch alarm coverage for all functions, table, and HTTP API (#126)
* Add CloudWatch alarm coverage for all functions, table, and HTTP API
Extend the in-template Lambda-<Metric>-<fn> alarm convention to full coverage:
- Errors (Sum, >=1/5min) for roster-sync and release-notifier, plus orphan
adoption of slack-bot and weekly-post (live alarms of those exact names
already exist outside the stack and must be deleted before deploy).
- Duration (Maximum, ~80% of timeout, 2-of-3) for all six functions.
- Throttles (Sum, >=1/5min) for all six functions.
- DynamoDB ThrottledRequests (Sum, >=1/5min) on afterhours-shifts. SystemErrors
omitted: AWS emits it only per-Operation, so a TableName-only alarm would sit
permanently in INSUFFICIENT_DATA.
- API Gateway v2 4xx (>=5), 5xx (>=1), and p99 Latency (~3000ms, 2-of-3) on the
implicit ServerlessHttpApi.
All alarms page the shared site-alerts SNS topic, no OKActions,
TreatMissingData notBreaching. README updated with a Monitoring & Alarms section.
Duration and API latency thresholds pending sign-off.
* Fix DynamoDB throttle alarm metric: use Read/WriteThrottleEvents
ThrottledRequests is not emitted at the TableName-only dimension (only
TableName+Operation), so the table-level alarm would sit permanently in
INSUFFICIENT_DATA and never fire. Replace with ReadThrottleEvents and
WriteThrottleEvents, which AWS/DynamoDB emits at the TableName dimension.
* Correct DynamoDB alarm docs and drop sign-off wording
README DynamoDB section now lists the alarms actually shipped
(DDB-ReadThrottle / DDB-WriteThrottle on Read/WriteThrottleEvents) instead
of the stale ThrottledRequests alarm. Thresholds are owner-approved, so
remove PENDING ADAM SIGN-OFF wording from template.yaml comments.
2026-06-17 14:45:47 -04:00
|
|
|
# ===========================================================================
|
|
|
|
|
# CloudWatch alarm coverage (Wave 1 PR A). All alarms page the same
|
|
|
|
|
# site-alerts SNS topic → AWS Chatbot → Slack as the existing Errors alarms.
|
|
|
|
|
# No OKActions by design (the existing Errors alarms have none either).
|
|
|
|
|
# Naming follows the in-template convention: Lambda-<Metric>-${Fn}.
|
|
|
|
|
# ===========================================================================
|
|
|
|
|
|
|
|
|
|
# --- Lambda Errors alarms (clone of HolidayRouterErrorAlarm) ---
|
|
|
|
|
# Errors for ring-scheduler + holiday-router already exist above.
|
|
|
|
|
|
|
|
|
|
RosterSyncErrorAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Errors-${RosterSyncFunction}"
|
|
|
|
|
AlarmDescription: "Roster sync Lambda reported one or more errors"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Errors
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref RosterSyncFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 1
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
ReleaseNotifierErrorAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Errors-${ReleaseNotifierFunction}"
|
|
|
|
|
AlarmDescription: "Release notifier Lambda reported one or more errors"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Errors
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref ReleaseNotifierFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 1
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
# ORPHAN ADOPTION: live alarms named Lambda-Errors-afterhours-shift-manager
|
|
|
|
|
# and Lambda-Errors-afterhours-weekly-post already exist OUTSIDE the stack.
|
|
|
|
|
# They MUST be deleted immediately before this stack deploys, or CloudFormation
|
|
|
|
|
# will fail to create these resources (AlarmName collision). See PR body.
|
|
|
|
|
SlackBotErrorAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Errors-${SlackBotFunction}"
|
|
|
|
|
AlarmDescription: "Slack bot Lambda reported one or more errors"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Errors
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref SlackBotFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 1
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
WeeklyPostErrorAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Errors-${WeeklyPostFunction}"
|
|
|
|
|
AlarmDescription: "Weekly post Lambda reported one or more errors"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Errors
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref WeeklyPostFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 1
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
# --- Lambda Duration alarms (Maximum, ms; 2-of-3 evaluation) ---
|
|
|
|
|
# Thresholds set to ~80% of each function's timeout, with 2-of-3 evaluation.
|
|
|
|
|
# Timeouts: slack-bot/weekly-post/release-notifier = 30s (global default);
|
|
|
|
|
# roster-sync/ring-scheduler/holiday-router = 60s.
|
|
|
|
|
SlackBotDurationAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Duration-${SlackBotFunction}"
|
|
|
|
|
AlarmDescription: "Slack bot Lambda duration approaching its 30s timeout (>=24s)"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Duration
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref SlackBotFunction
|
|
|
|
|
Statistic: Maximum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 3
|
|
|
|
|
DatapointsToAlarm: 2
|
|
|
|
|
Threshold: 24000
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
WeeklyPostDurationAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Duration-${WeeklyPostFunction}"
|
|
|
|
|
AlarmDescription: "Weekly post Lambda duration approaching its 30s timeout (>=24s)"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Duration
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref WeeklyPostFunction
|
|
|
|
|
Statistic: Maximum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 3
|
|
|
|
|
DatapointsToAlarm: 2
|
|
|
|
|
Threshold: 24000
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
RosterSyncDurationAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Duration-${RosterSyncFunction}"
|
|
|
|
|
AlarmDescription: "Roster sync Lambda duration approaching its 60s timeout (>=48s)"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Duration
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref RosterSyncFunction
|
|
|
|
|
Statistic: Maximum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 3
|
|
|
|
|
DatapointsToAlarm: 2
|
|
|
|
|
Threshold: 48000
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
RingSchedulerDurationAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Duration-${RingSchedulerFunction}"
|
|
|
|
|
AlarmDescription: "Ring scheduler Lambda duration approaching its 60s timeout (>=48s)"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Duration
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref RingSchedulerFunction
|
|
|
|
|
Statistic: Maximum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 3
|
|
|
|
|
DatapointsToAlarm: 2
|
|
|
|
|
Threshold: 48000
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
HolidayRouterDurationAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Duration-${HolidayRouterFunction}"
|
|
|
|
|
AlarmDescription: "Holiday router Lambda duration approaching its 60s timeout (>=48s)"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Duration
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref HolidayRouterFunction
|
|
|
|
|
Statistic: Maximum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 3
|
|
|
|
|
DatapointsToAlarm: 2
|
|
|
|
|
Threshold: 48000
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
ReleaseNotifierDurationAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Duration-${ReleaseNotifierFunction}"
|
|
|
|
|
AlarmDescription: "Release notifier Lambda duration approaching its 30s timeout (>=24s)"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Duration
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref ReleaseNotifierFunction
|
|
|
|
|
Statistic: Maximum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 3
|
|
|
|
|
DatapointsToAlarm: 2
|
|
|
|
|
Threshold: 24000
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
# --- Lambda Throttles alarms (Sum; threshold 1 over one 5-min period) ---
|
|
|
|
|
SlackBotThrottlesAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Throttles-${SlackBotFunction}"
|
|
|
|
|
AlarmDescription: "Slack bot Lambda was throttled (concurrency limit hit)"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Throttles
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref SlackBotFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 1
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
WeeklyPostThrottlesAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Throttles-${WeeklyPostFunction}"
|
|
|
|
|
AlarmDescription: "Weekly post Lambda was throttled (concurrency limit hit)"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Throttles
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref WeeklyPostFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 1
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
RosterSyncThrottlesAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Throttles-${RosterSyncFunction}"
|
|
|
|
|
AlarmDescription: "Roster sync Lambda was throttled (concurrency limit hit)"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Throttles
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref RosterSyncFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 1
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
RingSchedulerThrottlesAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Throttles-${RingSchedulerFunction}"
|
|
|
|
|
AlarmDescription: "Ring scheduler Lambda was throttled (concurrency limit hit)"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Throttles
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref RingSchedulerFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 1
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
HolidayRouterThrottlesAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Throttles-${HolidayRouterFunction}"
|
|
|
|
|
AlarmDescription: "Holiday router Lambda was throttled (concurrency limit hit)"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Throttles
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref HolidayRouterFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 1
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
ReleaseNotifierThrottlesAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "Lambda-Throttles-${ReleaseNotifierFunction}"
|
|
|
|
|
AlarmDescription: "Release notifier Lambda was throttled (concurrency limit hit)"
|
|
|
|
|
Namespace: AWS/Lambda
|
|
|
|
|
MetricName: Throttles
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: FunctionName
|
|
|
|
|
Value: !Ref ReleaseNotifierFunction
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 1
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
# --- DynamoDB alarms (afterhours-shifts table) ---
|
|
|
|
|
# ReadThrottleEvents / WriteThrottleEvents are the table-level throttle
|
|
|
|
|
# signals: AWS/DynamoDB emits them at the TableName dimension, so these
|
|
|
|
|
# alarms transition normally. (ThrottledRequests and SystemErrors are NOT
|
|
|
|
|
# emitted at TableName-only granularity — only at TableName+Operation — so
|
|
|
|
|
# alarms on them sit permanently in INSUFFICIENT_DATA and never fire.)
|
|
|
|
|
ShiftTableReadThrottleAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "DDB-ReadThrottle-${ShiftTable}"
|
|
|
|
|
AlarmDescription: "afterhours-shifts table had one or more read throttle events"
|
|
|
|
|
Namespace: AWS/DynamoDB
|
|
|
|
|
MetricName: ReadThrottleEvents
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: TableName
|
|
|
|
|
Value: !Ref ShiftTable
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
ShiftTableWriteThrottleAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "DDB-WriteThrottle-${ShiftTable}"
|
|
|
|
|
AlarmDescription: "afterhours-shifts table had one or more write throttle events"
|
|
|
|
|
Namespace: AWS/DynamoDB
|
|
|
|
|
MetricName: WriteThrottleEvents
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: TableName
|
|
|
|
|
Value: !Ref ShiftTable
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 0
|
|
|
|
|
ComparisonOperator: GreaterThanThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
# --- API Gateway v2 (HTTP API) alarms on the implicit ServerlessHttpApi ---
|
|
|
|
|
# AWS::ApiGatewayV2 metric names: 4xx, 5xx, Latency; dimension ApiId.
|
|
|
|
|
ApiGateway4xxAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "ApiGateway-4xx-${ServerlessHttpApi}"
|
|
|
|
|
AlarmDescription: "Elevated 4xx responses on the Slack events HTTP API"
|
|
|
|
|
Namespace: AWS/ApiGateway
|
|
|
|
|
MetricName: 4xx
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: ApiId
|
|
|
|
|
Value: !Ref ServerlessHttpApi
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 5
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
ApiGateway5xxAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "ApiGateway-5xx-${ServerlessHttpApi}"
|
|
|
|
|
AlarmDescription: "5xx responses on the Slack events HTTP API"
|
|
|
|
|
Namespace: AWS/ApiGateway
|
|
|
|
|
MetricName: 5xx
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: ApiId
|
|
|
|
|
Value: !Ref ServerlessHttpApi
|
|
|
|
|
Statistic: Sum
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 1
|
|
|
|
|
Threshold: 1
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
|
|
|
|
# Latency p99 via ExtendedStatistic. ~3000ms target chosen alongside the
|
|
|
|
|
# Lambda Duration thresholds (Slack requires a fast 3s ack).
|
|
|
|
|
ApiGatewayLatencyAlarm:
|
|
|
|
|
Type: AWS::CloudWatch::Alarm
|
|
|
|
|
Properties:
|
|
|
|
|
AlarmName: !Sub "ApiGateway-Latency-${ServerlessHttpApi}"
|
|
|
|
|
AlarmDescription: "p99 latency on the Slack events HTTP API exceeded 3s"
|
|
|
|
|
Namespace: AWS/ApiGateway
|
|
|
|
|
MetricName: Latency
|
|
|
|
|
Dimensions:
|
|
|
|
|
- Name: ApiId
|
|
|
|
|
Value: !Ref ServerlessHttpApi
|
|
|
|
|
ExtendedStatistic: p99
|
|
|
|
|
Period: 300
|
|
|
|
|
EvaluationPeriods: 3
|
|
|
|
|
DatapointsToAlarm: 2
|
|
|
|
|
Threshold: 3000
|
|
|
|
|
ComparisonOperator: GreaterThanOrEqualToThreshold
|
|
|
|
|
TreatMissingData: notBreaching
|
|
|
|
|
AlarmActions:
|
|
|
|
|
- !Sub "arn:aws:sns:${AWS::Region}:${AWS::AccountId}:site-alerts"
|
|
|
|
|
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
# --- CloudWatch Log Groups (explicit 60-day retention) ---
|
|
|
|
|
SlackBotLogGroup:
|
|
|
|
|
Type: AWS::Logs::LogGroup
|
|
|
|
|
Properties:
|
|
|
|
|
LogGroupName: !Sub "/aws/lambda/${SlackBotFunction}"
|
|
|
|
|
RetentionInDays: 60
|
|
|
|
|
|
|
|
|
|
WeeklyPostLogGroup:
|
|
|
|
|
Type: AWS::Logs::LogGroup
|
|
|
|
|
Properties:
|
|
|
|
|
LogGroupName: !Sub "/aws/lambda/${WeeklyPostFunction}"
|
|
|
|
|
RetentionInDays: 60
|
|
|
|
|
|
|
|
|
|
RosterSyncLogGroup:
|
|
|
|
|
Type: AWS::Logs::LogGroup
|
|
|
|
|
Properties:
|
|
|
|
|
LogGroupName: !Sub "/aws/lambda/${RosterSyncFunction}"
|
|
|
|
|
RetentionInDays: 60
|
|
|
|
|
|
|
|
|
|
RingSchedulerLogGroup:
|
|
|
|
|
Type: AWS::Logs::LogGroup
|
|
|
|
|
Properties:
|
|
|
|
|
LogGroupName: !Sub "/aws/lambda/${RingSchedulerFunction}"
|
|
|
|
|
RetentionInDays: 60
|
|
|
|
|
|
2026-06-17 11:14:29 -04:00
|
|
|
HolidayRouterLogGroup:
|
|
|
|
|
Type: AWS::Logs::LogGroup
|
|
|
|
|
Properties:
|
|
|
|
|
LogGroupName: !Sub "/aws/lambda/${HolidayRouterFunction}"
|
|
|
|
|
RetentionInDays: 60
|
|
|
|
|
|
Add changelog-driven releases and App Home tab (#112)
* Add changelog-driven releases and App Home tab
Version the bot continuously from CHANGELOG.md (the single source of
truth for both the version and the staff-readable notes) and surface
changes to users in two ways:
- A new afterhours-release-notifier Lambda posts a "What's New" message
to the shift channel on minor/major releases (patches stay silent).
- The bot gains an App Home "About" tab showing what it does, the
command list, and the current version's notes.
release.yaml runs on Deploy success (not release:published — GITHUB_TOKEN
events don't start downstream workflows), checks out the deployed commit,
and tags + publishes a GitHub Release + invokes the notifier. It assumes a
dedicated, boundary-carrying OIDC role scoped to InvokeFunction on the
notifier; the account's cfn role gates role creation on that boundary.
The manual Version Bump workflow is retired. A CI guard enforces that a
CHANGELOG edit is a clean SemVer bump and that the in-package copy matches.
* Harden release workflow and regex against CodeQL findings
Address three code-scanning alerts on the PR:
- Critical (actions/untrusted-checkout): split release.yaml into a
read-only `prepare` job that checks out and runs repo code, and a
privileged `publish` job (contents:write + OIDC) that never checks out
repo code — it tags, releases, and invokes purely through the GitHub
and AWS APIs. Also assert head_branch == main.
- High x2 (py/polynomial-redos): rewrite the italic and link regexes in
markdown_to_mrkdwn with possessive quantifiers and exclusive character
classes so they run in linear time on adversarial input. Adds a
regression test.
* Move release/announce into Deploy workflow to clear CodeQL
The workflow_run-triggered release.yaml kept tripping CodeQL's
privileged-context rules (untrusted-checkout, then cache-poisoning) —
CodeQL distrusts any workflow_run that checks out a ref, regardless of
the main-only guarantee, and there is no autofix.
Fold the release job into deploy.yaml gated on `needs: deploy`. A
push-to-main run is a trusted context, so checking out and running repo
code with write/OIDC is safe there. This still gates on deploy success
and serializes via the deploy concurrency group, and removes the
separate workflow entirely.
2026-06-11 19:41:31 -04:00
|
|
|
ReleaseNotifierLogGroup:
|
|
|
|
|
Type: AWS::Logs::LogGroup
|
|
|
|
|
Properties:
|
|
|
|
|
LogGroupName: !Sub "/aws/lambda/${ReleaseNotifierFunction}"
|
|
|
|
|
RetentionInDays: 60
|
|
|
|
|
|
2026-04-03 18:32:32 -04:00
|
|
|
Outputs:
|
|
|
|
|
SlackBotApiUrl:
|
|
|
|
|
Description: URL for Slack app Request URL configuration
|
|
|
|
|
Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/slack/events"
|
|
|
|
|
ShiftTableName:
|
|
|
|
|
Value: !Ref ShiftTable
|
|
|
|
|
SlackBotFunctionArn:
|
|
|
|
|
Value: !GetAtt SlackBotFunction.Arn
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
WeeklyPostFunctionArn:
|
|
|
|
|
Value: !GetAtt WeeklyPostFunction.Arn
|
2026-04-07 17:52:43 -04:00
|
|
|
RosterSyncFunctionArn:
|
|
|
|
|
Value: !GetAtt RosterSyncFunction.Arn
|
Merge ring-scheduler-3cx and resolve all open issues (#62)
* Add arm64, log retention, and compliance fixes
- Set arm64 architecture globally for all Lambda functions
- Add explicit CloudWatch log groups with 60-day retention
- Add missing WeeklyPostFunctionArn to stack outputs
- Add Dependabot assignees for both ecosystems
- Add samconfig.toml.example for onboarding
* Restructure src/ to per-function layout with shared Layer
Move from flat src/ to per-function directories:
- src/slack-bot/ — Slack Bolt Lambda handler
- src/weekly-post/ — Monday schedule + pay post
- src/roster-sync/ — Daily 3CX roster sync
- src/shared/ — Lambda Layer with schedule, blocks, three_cx_client
Each function has its own requirements.txt and CodeUri. Shared
modules are deployed as a SAM Layer (afterhours-shared) importable
as `from shared.X import Y`.
* Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager
- 3CX credentials (domain, client-id, client-secret) moved to
Secrets Manager under afterhours-shift-manager/3cx-* prefix
- Channel ID is now a non-secret CloudFormation parameter (ShiftChannel)
- Add shared secrets.py helper for Secrets Manager reads
- Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
* Merge ring-scheduler-3cx as 4th Lambda function
- Add afterhours-ring-scheduler Lambda with 4 EventBridge rules
(daily 8am EST/EDT + weekend 5pm EST/EDT) for 3CX ring group
routing updates
- Extract shared ring_scheduler.py module for direct ring group
updates from both the scheduled Lambda and the Slack bot
- Replace cross-Lambda invoke with direct update_ring_group() call
in the Slack bot — eliminates lambda:InvokeFunction dependency
- Use RingGroup API (correct) instead of Queue API (was wrong in
the original ring-scheduler repo)
- Eliminate YAML config fallback — DynamoDB is the sole schedule
source
- Add RingGroupNumber CloudFormation parameter
* Add schedule post live-update and old post deletion (#40, #41)
- Store schedule message timestamp in DynamoDB (SCHEDULE_POST record)
- Delete previous week's schedule post before posting the new one
- Live-update the schedule post via chat_update after any
pick/drop/swap/button-pickup so it always reflects current state
* Disallow past shifts and add day/night labels (#43, #42)
- Reject /oncall pick and /oncall drop for past dates
- Show ephemeral error when stale pickup buttons are clicked
- Hide pickup buttons for dates in the past
- Add explicit "Day (8am-5pm)" and "Night (5pm-8am)" labels to
schedule lines, pickup buttons, and shift change notifications
* Add admin slash commands for shift and roster management (#39)
- /oncall admin override <date> <ext> — assign a shift
- /oncall admin open <date> — mark shift as open
- /oncall admin clear <date> — remove override, revert to weekly
- /oncall admin roster add/remove/rename — manage roster entries
- Admin access gated by admin_users list in DynamoDB CONFIG
- Help message shows admin commands for admin users
* Update README for merged architecture and new features
* Switch from RingGroup API to Queue API at extension 801
The 3CX routing was changed from ring group 800 to queue 801 in a
previous PR on ring-scheduler-3cx. Updates all callers and the SAM
template parameter default accordingly.
* Pass SAM parameter overrides in deploy workflow
* Fix review findings: IAM, routing guards, past-date check, roster safety
- Ring scheduler: use DynamoDBCrudPolicy (resolve_shift needs Query)
- Button pickup: update 3CX for active shift type, not just night
- Pick/drop/swap commands: only update 3CX when shift type is active
- Swap command: add missing past-date guard
- add_roster_entry: reject if extension already exists
- Apply ruff formatting
* Add error handling to ring scheduler 3CX call
* Fix weekend day shift commands and admin 3CX routing
- Add _find_employee_shift() to check both day/night on weekends
- Drop/swap now correctly find and operate on weekend day shifts
- Pick finds first available shift type on weekends
- Admin override/open/clear update 3CX for same-day active shifts
* Fix dependabot directories and admin weekend shift handling
Dependabot now scans per-function requirement directories instead
of the repo root. Admin override/open/clear commands accept an
optional day/night parameter for weekend day shift management.
* Fix weekend day shift active window to 8am-5pm
Before midnight-8am on weekends incorrectly reported the day shift
as active when the previous night shift is still running.
* Show shift type label for both weekend shifts in notifications
Night shift notifications on weekends were missing the type label,
making them ambiguous. Also fix schedule post text fallback to use
this_monday instead of now for the start date.
* Extract determine_shift_type into shared layer
Eliminates duplicated weekend day/night boundary logic between
the ring scheduler and Slack bot Lambdas.
* Fix weekly schedule fallback start date
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Include weekend shift type in command confirmations
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
* Apply ruff formatting to app.py
* Only show day/night shift labels on weekends in schedule display
Weekday shifts are always night — the label was redundant clutter.
* Deduplicate 3CX forwarding payload and add shift type to pick command
Extract _update_forwarding helper in ThreeCXClient to share the
payload between queue and ring group methods. Add optional day/night
argument to /oncall pick so users can target a specific weekend shift.
* Consolidate WEEKEND_DAYS and fix weekday pickup button labels
Import WEEKEND_DAYS from shared.schedule instead of redefining in
blocks.py and weekly-post/app.py. Gate pickup button day/night
labels on weekends only, matching all other display surfaces.
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Adam Moussa <amoussa1229@users.noreply.github.com>
2026-05-12 19:55:39 -04:00
|
|
|
RingSchedulerFunctionArn:
|
|
|
|
|
Value: !GetAtt RingSchedulerFunction.Arn
|
2026-06-17 11:14:29 -04:00
|
|
|
HolidayRouterFunctionArn:
|
|
|
|
|
Value: !GetAtt HolidayRouterFunction.Arn
|
|
|
|
|
HolidaySchedulerExecutionRoleArn:
|
|
|
|
|
Description: Role EventBridge Scheduler assumes to invoke the holiday router; the slack-bot passes this when creating per-holiday schedules
|
|
|
|
|
Value: !GetAtt HolidaySchedulerExecutionRole.Arn
|
Add changelog-driven releases and App Home tab (#112)
* Add changelog-driven releases and App Home tab
Version the bot continuously from CHANGELOG.md (the single source of
truth for both the version and the staff-readable notes) and surface
changes to users in two ways:
- A new afterhours-release-notifier Lambda posts a "What's New" message
to the shift channel on minor/major releases (patches stay silent).
- The bot gains an App Home "About" tab showing what it does, the
command list, and the current version's notes.
release.yaml runs on Deploy success (not release:published — GITHUB_TOKEN
events don't start downstream workflows), checks out the deployed commit,
and tags + publishes a GitHub Release + invokes the notifier. It assumes a
dedicated, boundary-carrying OIDC role scoped to InvokeFunction on the
notifier; the account's cfn role gates role creation on that boundary.
The manual Version Bump workflow is retired. A CI guard enforces that a
CHANGELOG edit is a clean SemVer bump and that the in-package copy matches.
* Harden release workflow and regex against CodeQL findings
Address three code-scanning alerts on the PR:
- Critical (actions/untrusted-checkout): split release.yaml into a
read-only `prepare` job that checks out and runs repo code, and a
privileged `publish` job (contents:write + OIDC) that never checks out
repo code — it tags, releases, and invokes purely through the GitHub
and AWS APIs. Also assert head_branch == main.
- High x2 (py/polynomial-redos): rewrite the italic and link regexes in
markdown_to_mrkdwn with possessive quantifiers and exclusive character
classes so they run in linear time on adversarial input. Adds a
regression test.
* Move release/announce into Deploy workflow to clear CodeQL
The workflow_run-triggered release.yaml kept tripping CodeQL's
privileged-context rules (untrusted-checkout, then cache-poisoning) —
CodeQL distrusts any workflow_run that checks out a ref, regardless of
the main-only guarantee, and there is no autofix.
Fold the release job into deploy.yaml gated on `needs: deploy`. A
push-to-main run is a trusted context, so checking out and running repo
code with write/OIDC is safe there. This still gates on deploy success
and serializes via the deploy concurrency group, and removes the
separate workflow entirely.
2026-06-11 19:41:31 -04:00
|
|
|
ReleaseNotifierFunctionArn:
|
|
|
|
|
Value: !GetAtt ReleaseNotifierFunction.Arn
|
|
|
|
|
ReleaseNotifyInvokeRoleArn:
|
|
|
|
|
Description: Set this as the RELEASE_NOTIFY_INVOKE_ROLE_ARN repo variable for release.yaml
|
|
|
|
|
Value: !GetAtt ReleaseNotifyInvokeRole.Arn
|