fix: inline service registry event contracts

This commit is contained in:
Sai-karthik
2026-06-28 08:27:04 +00:00
parent 52589fc76d
commit 1b90e5db39
4 changed files with 170 additions and 12 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ const requiredServices = [
const directHealth = [ const directHealth = [
["interview", "http://127.0.0.1:8007/health"], ["interview", "http://127.0.0.1:8007/health"],
["roleplay", "http://127.0.0.1:8008/health"], ["roleplay", "http://127.0.0.1:8040/health"],
["resume", "http://127.0.0.1:8002/health"], ["resume", "http://127.0.0.1:8002/health"],
["qscore", "http://127.0.0.1:8000/health"], ["qscore", "http://127.0.0.1:8000/health"],
["courses", "http://127.0.0.1:8060/api/v1/health"], ["courses", "http://127.0.0.1:8060/api/v1/health"],
+155 -9
View File
@@ -62,6 +62,19 @@ export type ServiceRecord = {
qscoreSignals?: string[]; qscoreSignals?: string[];
usage: string; usage: string;
}; };
eventContract: {
canonicalServiceId: string;
openEvents: string[];
startedEvents: string[];
configuredEvents: string[];
completionEvents: string[];
abandonmentEvents: string[];
qscoreSignalIds: readonly string[];
analyticsDimensions: readonly string[];
requiredCorrelationFields: readonly string[];
handoffParams: readonly string[];
actionKind: "active_user_action" | "passive_background_action" | "measurement_review";
};
usageDocs: string[]; usageDocs: string[];
}; };
@@ -196,7 +209,7 @@ const serviceRegistry: ServiceRecord[] = [
media: "video", media: "video",
}, },
toolName: "prepare_interview_preview", toolName: "prepare_interview_preview",
completionEvents: ["interview.session.configured", "interview.feedback.generated", "interview.completed"], completionEvents: ["interview.session.configured", "interview.session.completed", "interview.feedback.generated"],
qscoreSignals: ["communication.interview", "proof.story_bank", "readiness.practice"], qscoreSignals: ["communication.interview", "proof.story_bank", "readiness.practice"],
usage: "Include missionInstanceId, missionId, stageId, curatorTaskId, role, and media when building stateful handoffs.", usage: "Include missionInstanceId, missionId, stageId, curatorTaskId, role, and media when building stateful handoffs.",
}, },
@@ -204,6 +217,19 @@ const serviceRegistry: ServiceRecord[] = [
"Call buildServiceLink('interview-service', 'preview', state) for curator handoffs.", "Call buildServiceLink('interview-service', 'preview', state) for curator handoffs.",
"Call getServiceEndpoint('interview-service', 'configure') for backend contract metadata.", "Call getServiceEndpoint('interview-service', 'configure') for backend contract metadata.",
], ],
eventContract: {
canonicalServiceId: "interview",
openEvents: ["task.opened", "interview.preview.opened"],
startedEvents: ["service.started", "interview.session.started"],
configuredEvents: ["service.configured", "interview.session.configured"],
completionEvents: ["service.completed", "interview.session.completed", "interview.feedback.generated"],
abandonmentEvents: ["service.abandoned", "interview.session.abandoned"],
qscoreSignalIds: ["interview.communication_score", "interview.confidence_score", "readiness.practice"],
analyticsDimensions: ["interview_type", "target_role", "difficulty", "duration_minutes", "overall_score"],
requiredCorrelationFields: ["taskId"],
handoffParams: ["curatorTaskId", "missionInstanceId", "missionId", "stageId"],
actionKind: "active_user_action",
},
}, },
{ {
id: "roleplay-service", id: "roleplay-service",
@@ -281,7 +307,7 @@ const serviceRegistry: ServiceRecord[] = [
mode: "video", mode: "video",
}, },
toolName: "prepare_roleplay_preview", toolName: "prepare_roleplay_preview",
completionEvents: ["roleplay.scenario.configured", "roleplay.feedback.generated", "roleplay.completed"], completionEvents: ["roleplay.scenario.configured", "roleplay.scenario.completed", "roleplay.feedback.generated"],
qscoreSignals: ["communication.roleplay", "networking.conversation", "readiness.practice"], qscoreSignals: ["communication.roleplay", "networking.conversation", "readiness.practice"],
usage: "Include role, brief, mission state, and curatorTaskId when building stateful handoffs.", usage: "Include role, brief, mission state, and curatorTaskId when building stateful handoffs.",
}, },
@@ -289,6 +315,19 @@ const serviceRegistry: ServiceRecord[] = [
"Call buildServiceLink('roleplay-service', 'builder', state) for curator handoffs.", "Call buildServiceLink('roleplay-service', 'builder', state) for curator handoffs.",
"Call getServiceEndpoint('roleplay-service', 'configure') for backend contract metadata.", "Call getServiceEndpoint('roleplay-service', 'configure') for backend contract metadata.",
], ],
eventContract: {
canonicalServiceId: "roleplay",
openEvents: ["task.opened", "roleplay.preview.opened"],
startedEvents: ["service.started", "roleplay.scenario.started"],
configuredEvents: ["service.configured", "roleplay.scenario.configured"],
completionEvents: ["service.completed", "roleplay.scenario.completed", "roleplay.feedback.generated"],
abandonmentEvents: ["service.abandoned", "roleplay.scenario.abandoned"],
qscoreSignalIds: ["roleplay.negotiation_confidence", "communication.roleplay", "readiness.practice"],
analyticsDimensions: ["roleplay_type", "target_role", "difficulty", "duration_minutes", "overall_score"],
requiredCorrelationFields: ["taskId"],
handoffParams: ["curatorTaskId", "missionInstanceId", "missionId", "stageId"],
actionKind: "active_user_action",
},
}, },
{ {
id: "resume-service", id: "resume-service",
@@ -356,7 +395,7 @@ const serviceRegistry: ServiceRecord[] = [
tab: "resumes", tab: "resumes",
}, },
toolName: "prepare_resume_upload", toolName: "prepare_resume_upload",
completionEvents: ["resume.analysis.completed", "resume.parsed", "resume.updated"], completionEvents: ["resume.analysis.completed", "resume.parsed", "resume.updated", "resume.exported"],
qscoreSignals: ["proof.resume", "readiness.ats", "profile.skills"], qscoreSignals: ["proof.resume", "readiness.ats", "profile.skills"],
usage: "Include mission state and optional section when linking into resume work.", usage: "Include mission state and optional section when linking into resume work.",
}, },
@@ -364,6 +403,19 @@ const serviceRegistry: ServiceRecord[] = [
"Call buildServiceLink('resume-service', 'workspace', { tab: 'resumes' }) for curator handoffs.", "Call buildServiceLink('resume-service', 'workspace', { tab: 'resumes' }) for curator handoffs.",
"Use the resume gateway proxy for browser calls that need Clerk auth.", "Use the resume gateway proxy for browser calls that need Clerk auth.",
], ],
eventContract: {
canonicalServiceId: "resume",
openEvents: ["task.opened"],
startedEvents: ["service.started", "resume.uploaded"],
configuredEvents: ["service.configured", "resume.parsed"],
completionEvents: ["service.completed", "resume.analysis.completed", "resume.updated", "resume.exported"],
abandonmentEvents: ["service.abandoned"],
qscoreSignalIds: ["resume.ats_score", "resume.profile_completeness", "proof.resume"],
analyticsDimensions: ["resume_id", "version_id", "ats_score", "role_fit_score", "updated_sections"],
requiredCorrelationFields: ["taskId"],
handoffParams: ["curatorTaskId", "missionInstanceId", "missionId", "stageId"],
actionKind: "active_user_action",
},
}, },
{ {
id: "cover-letter-service", id: "cover-letter-service",
@@ -424,7 +476,7 @@ const serviceRegistry: ServiceRecord[] = [
tab: "cover-letters", tab: "cover-letters",
}, },
toolName: "prepare_cover_letter_handoff", toolName: "prepare_cover_letter_handoff",
completionEvents: ["cover_letter.generated", "cover_letter.updated", "cover_letter.analysis_completed"], completionEvents: ["cover_letter.generated", "cover_letter.edited", "cover_letter.exported"],
qscoreSignals: ["proof.cover_letter", "readiness.application"], qscoreSignals: ["proof.cover_letter", "readiness.application"],
usage: "Use for application-specific artifact tasks; share mission/job context in query or payload.", usage: "Use for application-specific artifact tasks; share mission/job context in query or payload.",
}, },
@@ -432,6 +484,19 @@ const serviceRegistry: ServiceRecord[] = [
"Call buildServiceLink('cover-letter-service', 'workspace', { tab: 'cover-letters' }) for curator handoffs.", "Call buildServiceLink('cover-letter-service', 'workspace', { tab: 'cover-letters' }) for curator handoffs.",
"Backend endpoint metadata maps to resume-builder cover-letter APIs.", "Backend endpoint metadata maps to resume-builder cover-letter APIs.",
], ],
eventContract: {
canonicalServiceId: "cover_letter",
openEvents: ["task.opened"],
startedEvents: ["service.started"],
configuredEvents: ["service.configured"],
completionEvents: ["service.completed", "cover_letter.generated", "cover_letter.edited", "cover_letter.exported"],
abandonmentEvents: ["service.abandoned"],
qscoreSignalIds: ["proof.cover_letter", "readiness.application"],
analyticsDimensions: ["cover_letter_id", "role_fit_score", "export_format"],
requiredCorrelationFields: ["taskId"],
handoffParams: ["curatorTaskId", "missionInstanceId", "missionId", "stageId"],
actionKind: "active_user_action",
},
}, },
{ {
id: "courses-service", id: "courses-service",
@@ -482,11 +547,24 @@ const serviceRegistry: ServiceRecord[] = [
start: "Start course", start: "Start course",
}, },
toolName: "prepare_course_handoff", toolName: "prepare_course_handoff",
completionEvents: ["course.started", "course.completed"], completionEvents: ["course.generated", "course.started", "course.lesson.completed", "course.completed", "course.weakness.applied"],
qscoreSignals: ["skills.learning", "readiness.course_progress"], qscoreSignals: ["skills.learning", "readiness.course_progress"],
usage: "Use for skill-gap tasks that should become learning work.", usage: "Use for skill-gap tasks that should become learning work.",
}, },
usageDocs: ["Call buildServiceLink('courses-service', 'catalog', state) for course handoffs."], usageDocs: ["Call buildServiceLink('courses-service', 'catalog', state) for course handoffs."],
eventContract: {
canonicalServiceId: "course",
openEvents: ["task.opened"],
startedEvents: ["service.started", "course.started"],
configuredEvents: ["course.generated"],
completionEvents: ["service.completed", "course.lesson.completed", "course.completed", "course.weakness.applied"],
abandonmentEvents: ["service.abandoned"],
qscoreSignalIds: ["course.skill_gap_closed", "skills.learning", "readiness.course_progress"],
analyticsDimensions: ["course_id", "lesson_id", "progress_percentage", "weakness_tags"],
requiredCorrelationFields: ["taskId"],
handoffParams: ["curatorTaskId", "missionInstanceId", "missionId", "stageId"],
actionKind: "active_user_action",
},
}, },
{ {
id: "assessment-service", id: "assessment-service",
@@ -528,11 +606,24 @@ const serviceRegistry: ServiceRecord[] = [
review: "Review assessment", review: "Review assessment",
}, },
toolName: "prepare_assessment_handoff", toolName: "prepare_assessment_handoff",
completionEvents: ["assessment.started", "assessment.submitted", "assessment.completed"], completionEvents: ["assessment.started", "assessment.completed"],
qscoreSignals: ["skills.assessment", "readiness.measurement"], qscoreSignals: ["skills.assessment", "readiness.measurement"],
usage: "Use when a task needs a scoreable assessment rather than practice.", usage: "Use when a task needs a scoreable assessment rather than practice.",
}, },
usageDocs: ["Call buildServiceLink('assessment-service', 'assessment', state) for assessment handoffs."], usageDocs: ["Call buildServiceLink('assessment-service', 'assessment', state) for assessment handoffs."],
eventContract: {
canonicalServiceId: "assessment",
openEvents: ["task.opened"],
startedEvents: ["service.started", "assessment.started"],
configuredEvents: [],
completionEvents: ["service.completed", "assessment.completed"],
abandonmentEvents: ["service.abandoned"],
qscoreSignalIds: ["skills.assessment", "readiness.measurement"],
analyticsDimensions: ["assessment_id", "skill_area", "score", "weakness_tags"],
requiredCorrelationFields: ["taskId"],
handoffParams: ["curatorTaskId", "missionInstanceId", "missionId", "stageId"],
actionKind: "measurement_review",
},
}, },
{ {
id: "matchmaking-service", id: "matchmaking-service",
@@ -577,7 +668,15 @@ const serviceRegistry: ServiceRecord[] = [
review: "Review matches", review: "Review matches",
}, },
toolName: "prepare_matchmaking_handoff", toolName: "prepare_matchmaking_handoff",
completionEvents: ["matchmaking.feed_viewed", "matchmaking.match_saved", "matchmaking.preference_updated"], completionEvents: [
"matchmaking.matches.generated",
"matchmaking.feed.viewed",
"matchmaking.match.viewed",
"matchmaking.match.saved",
"matchmaking.match.dismissed",
"matchmaking.application.started",
"matchmaking.application.completed",
],
qscoreSignals: ["market.matches", "networking.opportunities"], qscoreSignals: ["market.matches", "networking.opportunities"],
usage: "Use for immediate opportunity matching or mentor/network suggestions.", usage: "Use for immediate opportunity matching or mentor/network suggestions.",
}, },
@@ -585,6 +684,26 @@ const serviceRegistry: ServiceRecord[] = [
"Call buildServiceLink('matchmaking-service', 'jobs', state) for job matching.", "Call buildServiceLink('matchmaking-service', 'jobs', state) for job matching.",
"Use the canonical /agents/matchmaking page for Scout-style opportunity review.", "Use the canonical /agents/matchmaking page for Scout-style opportunity review.",
], ],
eventContract: {
canonicalServiceId: "matchmaking",
openEvents: ["task.opened", "matchmaking.feed.viewed"],
startedEvents: ["service.started", "matchmaking.application.started"],
configuredEvents: [],
completionEvents: [
"service.completed",
"matchmaking.matches.generated",
"matchmaking.match.viewed",
"matchmaking.match.saved",
"matchmaking.match.dismissed",
"matchmaking.application.completed",
],
abandonmentEvents: ["service.abandoned"],
qscoreSignalIds: ["matchmaking.role_fit", "market.matches", "networking.opportunities"],
analyticsDimensions: ["match_count", "role_ids", "role_fit", "saved_count", "dismissed_reason"],
requiredCorrelationFields: ["taskId"],
handoffParams: ["curatorTaskId", "missionInstanceId", "missionId", "stageId"],
actionKind: "active_user_action",
},
}, },
{ {
id: "qscore-service", id: "qscore-service",
@@ -625,11 +744,24 @@ const serviceRegistry: ServiceRecord[] = [
review: "Review QScore", review: "Review QScore",
}, },
toolName: "prepare_qscore_review", toolName: "prepare_qscore_review",
completionEvents: ["qscore.updated", "qscore.signal.projected"], completionEvents: ["qscore.baseline.created", "qscore.review.opened", "qscore.weak_driver.reviewed", "qscore.signal.projected", "qscore.updated"],
qscoreSignals: ["qscore.updated", "qscore.signal.projected"], qscoreSignals: ["qscore.updated", "qscore.signal.projected"],
usage: "Use for measurement and projected readiness review tasks.", usage: "Use for measurement and projected readiness review tasks.",
}, },
usageDocs: ["Call buildServiceLink('qscore-service', 'dashboard', state) for QScore handoffs."], usageDocs: ["Call buildServiceLink('qscore-service', 'dashboard', state) for QScore handoffs."],
eventContract: {
canonicalServiceId: "qscore",
openEvents: ["qscore.review.opened"],
startedEvents: ["service.started"],
configuredEvents: ["qscore.baseline.created"],
completionEvents: ["qscore.signal.projected", "qscore.updated", "qscore.weak_driver.reviewed"],
abandonmentEvents: [],
qscoreSignalIds: ["qscore.updated", "qscore.signal.projected"],
analyticsDimensions: ["score_before", "score_after", "signal_id", "delta", "trend_label"],
requiredCorrelationFields: [],
handoffParams: [],
actionKind: "measurement_review",
},
}, },
{ {
id: "social-branding-service", id: "social-branding-service",
@@ -672,11 +804,24 @@ const serviceRegistry: ServiceRecord[] = [
review: "Review profile", review: "Review profile",
}, },
toolName: "prepare_social_branding_handoff", toolName: "prepare_social_branding_handoff",
completionEvents: ["social.profile_updated", "social.linkedin_connected", "social.branding_analyzed"], completionEvents: ["brand.post.drafted", "brand.post.edited", "brand.post.exported", "brand.profile.updated", "brand.content.published"],
qscoreSignals: ["profile.linkedin", "proof.visibility", "networking.brand"], qscoreSignals: ["profile.linkedin", "proof.visibility", "networking.brand"],
usage: "Use for profile visibility, LinkedIn cleanup, and social proof tasks.", usage: "Use for profile visibility, LinkedIn cleanup, and social proof tasks.",
}, },
usageDocs: ["Call buildServiceLink('social-branding-service', 'profile', state) for social branding handoffs."], usageDocs: ["Call buildServiceLink('social-branding-service', 'profile', state) for social branding handoffs."],
eventContract: {
canonicalServiceId: "brand",
openEvents: ["task.opened"],
startedEvents: ["service.started"],
configuredEvents: ["brand.post.drafted"],
completionEvents: ["service.completed", "brand.post.edited", "brand.post.exported", "brand.profile.updated", "brand.content.published"],
abandonmentEvents: ["service.abandoned"],
qscoreSignalIds: ["brand.visibility_signal", "profile.linkedin", "proof.visibility"],
analyticsDimensions: ["content_type", "channel", "draft_id", "topic", "profile_field_updated"],
requiredCorrelationFields: ["taskId"],
handoffParams: ["curatorTaskId", "missionInstanceId", "missionId", "stageId"],
actionKind: "active_user_action",
},
}, },
]; ];
@@ -751,6 +896,7 @@ export function listServicesForCatalog() {
}, },
frontend: service.frontend, frontend: service.frontend,
curator: service.curator, curator: service.curator,
eventContract: service.eventContract,
usageDocs: service.usageDocs, usageDocs: service.usageDocs,
})); }));
} }
+12 -2
View File
@@ -25,12 +25,22 @@ const VALID_COMPLETION_TYPES = [
"resume.analysis.completed", "resume.analysis.completed",
"resume.parsed", "resume.parsed",
"resume.updated", "resume.updated",
"resume.exported",
"interview.session.configured", "interview.session.configured",
"interview.session.completed",
"interview.feedback.generated", "interview.feedback.generated",
"interview.completed",
"roleplay.scenario.configured", "roleplay.scenario.configured",
"roleplay.scenario.completed",
"roleplay.feedback.generated", "roleplay.feedback.generated",
"roleplay.completed", "assessment.completed",
"course.completed",
"course.lesson.completed",
"brand.post.exported",
"brand.profile.updated",
"matchmaking.matches.generated",
"matchmaking.feed.viewed",
"matchmaking.match.saved",
"matchmaking.application.completed",
"qscore.updated", "qscore.updated",
"qscore.signal.projected", "qscore.signal.projected",
"curator.task.completed", "curator.task.completed",
+2
View File
@@ -17,6 +17,7 @@ export type ServiceCapability = {
backend?: unknown; backend?: unknown;
frontend?: unknown; frontend?: unknown;
curator?: unknown; curator?: unknown;
eventContract?: unknown;
usageDocs?: string[]; usageDocs?: string[];
}; };
@@ -43,6 +44,7 @@ export function listServiceCapabilities(opts: { public?: boolean } = {}): Servic
}, },
frontend: service.frontend, frontend: service.frontend,
curator: service.curator, curator: service.curator,
eventContract: service.eventContract,
usageDocs: service.usageDocs, usageDocs: service.usageDocs,
})); }));