Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions infra/lambda/poller/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -594,10 +594,12 @@ def build_active_plan_ride_index(
recipients = [user_id] + sorted(
s for s in subscribers if s and s != user_id
)
# Rides dropped via the /replan approve flow leave the watch
# set (atomic dropped_ride_ids set — never mutates
# Rides dropped OR marked done via /replan leave the watch set
# (atomic dropped_ride_ids / completed_ride_ids — never mutate
# ride_sequence, so the MCP planner's view is intact).
dropped = item.get("dropped_ride_ids") or set()
dropped = (item.get("dropped_ride_ids") or set()) | (
item.get("completed_ride_ids") or set()
)
# Remaining planned rides with their at-plan-time predictions,
# for the plan-drift check (current waits vs what the plan
# assumed). Only rides carrying a numeric predicted_wait_min
Expand Down
20 changes: 20 additions & 0 deletions infra/lib/disney-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ import * as fs from "fs";
* (one-time `aws ssm put-parameter`) so secrets never live in CDK,
* CloudFormation, or git. Same hygiene pattern as the earlier project's TMDB key. */
const PUSHOVER_APP_TOKEN_PARAM = "/disney/pushover/app_token";
// Anthropic API key for the /replan "Ask Claude" suggestion (server-side
// Sonnet call). SecureString in SSM; only the NAME is in env, the value
// never leaves SSM. Populate the value out-of-band (not in the repo).
const ANTHROPIC_API_KEY_PARAM = "/magicmonitor/anthropic-api-key";
const PUSHOVER_USER_KEY_PARAM = "/disney/pushover/megan_user_key";

/** Park entity IDs from themeparks.wiki — duplicated here from the Lambda
Expand Down Expand Up @@ -592,6 +596,9 @@ export class DisneyStack extends cdk.Stack {
// PARAMETER NAME is in env — the secret value never leaves
// SSM. Rotates without a redeploy.
PUSHOVER_APP_TOKEN_PARAM,
// Anthropic key param name for the /replan "Ask Claude" call
// (value read from SSM at runtime via the grant below).
ANTHROPIC_API_KEY_PARAM,
AMPLIFY_MONOREPO_APP_ROOT: "web",
AMPLIFY_DIFF_DEPLOY: "false",
// Avoid the "do you accept telemetry?" interactive prompt
Expand Down Expand Up @@ -651,6 +658,7 @@ export class DisneyStack extends cdk.Stack {
// NEXTAUTH_* are kept as v4-compat fallbacks.
"echo \"DISNEY_TABLE_NAME=$DISNEY_TABLE_NAME\" >> .env.production",
"echo \"PUSHOVER_APP_TOKEN_PARAM=$PUSHOVER_APP_TOKEN_PARAM\" >> .env.production",
"echo \"ANTHROPIC_API_KEY_PARAM=$ANTHROPIC_API_KEY_PARAM\" >> .env.production",
"echo \"AUTH_URL=$NEXTAUTH_URL\" >> .env.production",
"echo \"AUTH_SECRET=$NEXTAUTH_SECRET\" >> .env.production",
"echo \"NEXTAUTH_URL=$NEXTAUTH_URL\" >> .env.production",
Expand Down Expand Up @@ -807,6 +815,18 @@ export class DisneyStack extends cdk.Stack {
}),
);

// SSM read for the Anthropic API key — used by the /replan
// "Ask Claude" server action (server-side Sonnet call). Same tight
// one-parameter scoping as the Pushover grant.
webApp.computeRole.addToPrincipalPolicy(
new iam.PolicyStatement({
actions: ["ssm:GetParameter"],
resources: [
`arn:aws:ssm:${this.region}:${this.account}:parameter${ANTHROPIC_API_KEY_PARAM}`,
],
}),
);

// CloudWatch Logs permissions for the SSR compute role. Newer
// alpha versions of @aws-cdk/aws-amplify-alpha don't auto-attach
// these (the earlier project's deploy at v2.251.0 picked up an
Expand Down
18 changes: 15 additions & 3 deletions mcp/_tool_impls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2706,11 +2706,23 @@ def split_dropped_rides(
sequence — not a stale one that still lists a ride the family dropped.
"""
dropped_ids = set(plan.get("dropped_ride_ids") or [])
# Rides marked done from /replan also leave the remaining sequence
# (the plan's completed_rides list is the richer, calibration path).
done_ids = set(plan.get("completed_ride_ids") or [])
seq = plan.get("ride_sequence") or []
if not dropped_ids:
return list(seq), []
still = [r for r in seq if r.get("ride_id") not in dropped_ids]
still = [
r for r in seq
if r.get("ride_id") not in dropped_ids and r.get("ride_id") not in done_ids
]
dropped = [r for r in seq if r.get("ride_id") in dropped_ids]
# Honor a Claude-applied re-order (plan_order, set by the /replan
# "Ask Claude" apply): listed rides first, in that order; unlisted
# rides keep their original position after. Keeps the planner's view
# consistent with what the family sees on the page.
order = plan.get("plan_order") or []
if order:
rank = {rid: i for i, rid in enumerate(order)}
still.sort(key=lambda r: rank.get(r.get("ride_id"), len(order) + 1))
return still, dropped


Expand Down
26 changes: 26 additions & 0 deletions mcp/tests/test_trip_planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -785,3 +785,29 @@ def test_garbage_returns_none(self):
import _tool_impls
assert _tool_impls.parse_ll_time("later", "2026-07-03") is None
assert _tool_impls.parse_ll_time("", "2026-07-03") is None


class TestPlanOrderHonored:
"""split_dropped_rides applies a Claude-set plan_order so the MCP view
matches what the family reordered on /replan."""

def test_reorders_still_by_plan_order(self):
import _tool_impls
plan = {
"ride_sequence": [{"ride_id": "a"}, {"ride_id": "b"}, {"ride_id": "c"}],
"plan_order": ["c", "a"],
}
still, _ = _tool_impls.split_dropped_rides(plan)
# c, a first (in order), then b (unlisted) keeps trailing.
assert [r["ride_id"] for r in still] == ["c", "a", "b"]

def test_order_plus_drop(self):
import _tool_impls
plan = {
"ride_sequence": [{"ride_id": "a"}, {"ride_id": "b"}, {"ride_id": "c"}],
"plan_order": ["c", "b", "a"],
"dropped_ride_ids": ["b"],
}
still, dropped = _tool_impls.split_dropped_rides(plan)
assert [r["ride_id"] for r in still] == ["c", "a"] # b dropped
assert [r["ride_id"] for r in dropped] == ["b"]
1 change: 1 addition & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"test": "vitest run"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.110.0",
"@aws-sdk/client-dynamodb": "^3.700.0",
"@aws-sdk/client-ssm": "^3.1041.0",
"@aws-sdk/lib-dynamodb": "^3.700.0",
Expand Down
Loading
Loading