fix(curator): preserve active sprint on reads

This commit is contained in:
-Puter
2026-07-15 16:07:46 +05:30
parent e8c6665ca0
commit d4332f9f8b
2 changed files with 45 additions and 1 deletions
@@ -6,6 +6,7 @@ import {
decideCuratorSprintReconciliation,
resolveCuratorSprintAccess,
shouldReconcileCuratorSprint,
shouldRebaseCuratorSprintForOnboardingContext,
} from "../src/v1/curator/curator-store.js";
import { templateSetFor, trialDaysFor } from "../src/v1/curator/task-registry.js";
@@ -51,6 +52,26 @@ const startDate = "2026-07-13";
const studentFingerprint = curatorSprintPlanFingerprint({ startDate, variantId: "student_recent_grad", durationDays: 2, access: "trial" });
const experiencedFingerprint = curatorSprintPlanFingerprint({ startDate, variantId: "experienced_professional", durationDays: 2, access: "trial" });
const experiencedPlan = curatorSprintTaskPlan({ startDate, variantId: "experienced_professional", durationDays: 2, access: "trial" });
assert.equal(
shouldRebaseCuratorSprintForOnboardingContext({
existingStartDate: "2026-07-14",
focusDate: "2026-07-15",
onboardingContextProvided: true,
existingCompatible: true,
}),
false,
"a normal sprint read with onboarding context must preserve the active sprint across days",
);
assert.equal(
shouldRebaseCuratorSprintForOnboardingContext({
existingStartDate: "2026-07-14",
focusDate: "2026-07-15",
onboardingContextProvided: true,
existingCompatible: false,
}),
true,
"changed onboarding compatibility may replace the existing sprint without resetting its calendar identity",
);
assert.notEqual(studentFingerprint, experiencedFingerprint, "ICP changes must change the compatibility fingerprint");
assert.equal(shouldReconcileCuratorSprint(
{ access: "trial", durationDays: 2, variantId: "student_recent_grad", planFingerprint: studentFingerprint },
+24 -1
View File
@@ -141,6 +141,20 @@ export function decideCuratorSprintReconciliation(
};
}
export function shouldRebaseCuratorSprintForOnboardingContext(input: {
existingStartDate?: string;
focusDate: string;
onboardingContextProvided: boolean;
existingCompatible: boolean;
}) {
return Boolean(
input.onboardingContextProvided
&& input.existingStartDate
&& input.existingStartDate !== input.focusDate
&& !input.existingCompatible,
);
}
function accessChoiceFromContext(context: Record<string, unknown> | undefined): "trial" | "full" | undefined {
const preferences = context && context.preferences && typeof context.preferences === "object" && !Array.isArray(context.preferences)
? context.preferences as Record<string, unknown>
@@ -751,7 +765,16 @@ async function loadSprintState(
const existingDuration = latestPayload?.durationDays === 7 ? 7 : latestPayload?.durationDays === 2 ? 2 : undefined;
const existingAccess = latestPayload?.access === "full" ? "full" : latestPayload?.access === "trial" ? "trial" : undefined;
const existingFingerprint = typeof latestPayload?.planFingerprint === "string" ? latestPayload.planFingerprint : undefined;
const rebaseExistingForCompletion = Boolean(onboardingContextOverride && existingStartDate && existingStartDate !== todayDate);
const existingCompatible = Boolean(
existingStartDate && existingVersion === CURATOR_PLAN_VERSION && existingVariant
&& existingDuration === desired.durationDays && existingAccess === desired.access,
);
const rebaseExistingForCompletion = shouldRebaseCuratorSprintForOnboardingContext({
existingStartDate,
focusDate: todayDate,
onboardingContextProvided: Boolean(onboardingContextOverride),
existingCompatible,
});
const reuseDecision = existingStartDate && existingVersion && existingDuration && existingAccess
? resolveAgedTrialReuse({
existing: { startDate: existingStartDate, version: existingVersion, durationDays: existingDuration, access: existingAccess },