refactor: simplify onboarding cache contracts

This commit is contained in:
Jesse_Chen
2026-07-20 00:07:24 +08:00
parent 346ec02fd2
commit a9ffbd9e0a
5 changed files with 30 additions and 61 deletions
@@ -263,3 +263,31 @@ Staged follow-up audit ran `git diff --cached --check`, `--name-status`, and `--
passed, and the staged set contained exactly nine follow-up paths: this report, the production
route adapter, handler, payload boundary, route fake/test, policy test, candidate test, and root CI
contract. Unrelated `.superpowers/sdd/task-1-report.md` and `.omo/` remained unstaged.
## Review cleanup: behavioral authority
The post-review cleanup removes implementation-text assertions from the root Python contract and
leaves stateful TypeScript route tests as the authority for cache claim and completion behavior.
Broad UI, authentication, migration, and structured suggestion-marker coverage remains in the
Python contract; exact natural-language prompt prose is no longer pinned there.
The one-use `createOnboardingCompletionTransition` wrapper and its tautological unit test were
removed. The handler now passes `identity.pendingVersion` and `identity.readyVersion` directly to
the repository completion command. This is a refactor only, so the existing route integration
matrix provided the behavior-preservation check rather than adding a new RED case.
### Cleanup verification
- Focused route, policy, and candidate-completion tests: exit 0; 30 passed, 0 failed.
- Full frontend suite: exit 0; 454 passed, 0 failed.
- Python agent-chat contract: exit 0; 2 passed.
- Changed-file ESLint and Ruff: exit 0 with zero diagnostics.
- Webpack production build: exit 0; compiled successfully, TypeScript completed, and 22/22 pages
generated.
- `git diff --check`: exit 0.
- Pure LOC: cache policy 70, onboarding handler 182, policy test 154, and root contract 69; every
modified code/test file remains below 200 pure LOC.
The build continues to use repository-standard CI placeholder Supabase values and webpack because
the worktree's external dependency symlink is incompatible with default Turbopack. No new concern
was introduced by this cleanup.
@@ -19,11 +19,6 @@ export type OnboardingCacheIdentity = {
readonly pendingVersion: string;
};
export type OnboardingCompletionTransition = {
readonly expectedVersion: string;
readonly readyVersion: string;
};
type OnboardingCacheObservation<Payload> = {
readonly identity: OnboardingCacheIdentity;
readonly observedVersion: string | null;
@@ -83,12 +78,3 @@ export function decideOnboardingCache<Payload>(
pendingVersion: observation.identity.pendingVersion,
};
}
export function createOnboardingCompletionTransition(
identity: OnboardingCacheIdentity,
): OnboardingCompletionTransition {
return {
expectedVersion: identity.pendingVersion,
readyVersion: identity.readyVersion,
};
}
+2 -4
View File
@@ -1,6 +1,5 @@
import {
createOnboardingCacheIdentity,
createOnboardingCompletionTransition,
decideOnboardingCache,
} from "./onboarding-cache-policy.ts";
import {
@@ -181,11 +180,10 @@ export function createOnboardingPost(dependencies: OnboardingPostDependencies):
);
}
const completion = createOnboardingCompletionTransition(identity);
const completed = await session.repository.completeProfile({
userId: session.userId,
expectedPendingVersion: completion.expectedVersion,
readyVersion: completion.readyVersion,
expectedPendingVersion: identity.pendingVersion,
readyVersion: identity.readyVersion,
payload,
generatedAt: dependencies.now().toISOString(),
});
@@ -2,7 +2,6 @@ import assert from "node:assert/strict";
import test from "node:test";
import {
createOnboardingCacheIdentity,
createOnboardingCompletionTransition,
decideOnboardingCache,
ONBOARDING_CLAIM_TTL_MS,
} from "../src/lib/onboarding-cache-policy.ts";
@@ -64,22 +63,6 @@ test("changed profile cannot wait on the previous profile's active pending claim
});
});
test("stale profile completion loses ownership after the current profile claims", () => {
// Given: B has replaced A's pending identity in the row.
const identityA = createOnboardingCacheIdentity(profileA);
const identityB = createOnboardingCacheIdentity({ ...profileA, cityCode: "310100" });
const rowVersionAfterBClaims = identityB.pendingVersion;
// When: each completion prepares an exact compare-and-set transition.
const completionA = createOnboardingCompletionTransition(identityA);
const completionB = createOnboardingCompletionTransition(identityB);
// Then: A cannot match the row, while B can commit its own ready identity.
assert.notEqual(rowVersionAfterBClaims, completionA.expectedVersion);
assert.equal(rowVersionAfterBClaims, completionB.expectedVersion);
assert.equal(completionB.readyVersion, identityB.readyVersion);
});
test("current profile accepts only valid ready content and an active current pending claim", () => {
// Given: one current profile identity and a valid cached payload.
const identity = createOnboardingCacheIdentity(profileA);
-26
View File
@@ -5,10 +5,6 @@ PAGE = ROOT / "frontend" / "src" / "app" / "page.tsx"
AGENT = ROOT / "frontend" / "src" / "mastra" / "index.ts"
ONBOARDING_ROUTE = ROOT / "frontend" / "src" / "app" / "api" / "onboarding" / "route.ts"
ONBOARDING_CLIENT = ROOT / "frontend" / "src" / "lib" / "onboarding-client.ts"
ONBOARDING_POST = ROOT / "frontend" / "src" / "lib" / "onboarding-post.ts"
ONBOARDING_CACHE_POLICY = (
ROOT / "frontend" / "src" / "lib" / "onboarding-cache-policy.ts"
)
CONSULT_ROUTE = ROOT / "frontend" / "src" / "app" / "api" / "consult" / "route.ts"
MODELS_ROUTE = ROOT / "frontend" / "src" / "app" / "api" / "models" / "route.ts"
MODEL_SELECTION = ROOT / "frontend" / "src" / "lib" / "consultation-model-selection.ts"
@@ -29,8 +25,6 @@ def test_onboarding_and_agent_suggestion_contract() -> None:
agent = AGENT.read_text(encoding="utf-8")
route = ONBOARDING_ROUTE.read_text(encoding="utf-8")
onboarding_client = ONBOARDING_CLIENT.read_text(encoding="utf-8")
onboarding_post = ONBOARDING_POST.read_text(encoding="utf-8")
cache_policy = ONBOARDING_CACHE_POLICY.read_text(encoding="utf-8")
consult_route = CONSULT_ROUTE.read_text(encoding="utf-8")
migration = ONBOARDING_MIGRATION.read_text(encoding="utf-8")
@@ -54,32 +48,12 @@ def test_onboarding_and_agent_suggestion_contract() -> None:
assert "export function getOnboardingAgent" in agent
assert "skills: [jyotishSkillPath]" in agent
assert "This is onboarding, not a chart reading" in agent
assert "<!--AYANAM_SUGGESTIONS:" in agent
assert "grounded in the answer just given" in agent
assert "Treat the server-provided current time as authoritative" in agent
assert "supabase.auth.getUser()" in route
assert "function currentTimeContext(now = new Date())" in consult_route
assert "currentTimeContext(requestTime)," in consult_route
assert "中国标准时间(UTC+8" in consult_route
assert "export const POST = createOnboardingPost" in route
assert 'claim.is("onboarding_version", null)' in route
assert '.eq("onboarding_version", command.expectedVersion)' in route
assert 'claim.is("onboarding_generated_at", null)' in route
assert '.eq("onboarding_generated_at", command.expectedGeneratedAt)' in route
assert (
'.eq("onboarding_version", command.expectedPendingVersion)\n'
' .select("id")\n'
" .maybeSingle();"
) in route
assert "createOnboardingCacheIdentity" in onboarding_post
assert "expectedGeneratedAt: profile.onboarding_generated_at" in onboarding_post
assert "completed.data" in onboarding_post
assert 'source: "cache"' in onboarding_post
assert 'source: "pending"' in onboarding_post
assert 'createHash("sha256")' in cache_policy
assert "getOnboardingAgent(model).generate" in route
assert "onboarding_payload" in migration
assert "to service_role" in migration
assert "to authenticated" not in migration