Appearance
Detailed client profiles collect the baseline context a coach needs before assigning training, nutrition, or feedback.
The profile is the shared source of client context across onboarding, program setup, nutrition planning, progress review, and communication. It must separate client-owned baseline data from coach-scoped requirements, private coach notes, and readiness review decisions.
Tags: NestJS · Profile Requirements · Drizzle + PostgreSQL · Coach Web + Mobile
V1 baseline decisions
a client can have only one active or onboarding coach relationship, so there are no concurrent-coach profile sharing controls or relationship-specific profile copies. Coaches choose simple package templates, not field-by-field requirement rules. Coach edits to client-visible fields need audit attribution, not client confirmation. Relationship-scoped private notes and health profile history are retained for 30 days after a relationship ends, then purged or anonymized unless account deletion policy requires a shorter path.
Offline Sync Protocol
Profile sections own their field validation and review rules. Mobile draft persistence, section update operation ids, receipts, conflicts, encryption posture, and purge triggers follow the Offline Sync Protocol.
Profile Creation And Review Flows
Profile data starts during invitation/onboarding and becomes the coach's operational baseline for client decisions.
Coach Creates Or Invites Client
- Coach starts adding a client from web or coach mobile.
- Coach selects a simple onboarding package template such as baseline, training, nutrition, or combined coaching. The template expands into the required profile field groups.
- API creates or updates the
ProfileRequirementSetfor the coach-client relationship and attaches it to the onboarding workflow. - Client invitation and onboarding tasks reference the requirement set so the client sees only the fields needed for that relationship.
- Coach can save a draft client record for manual follow-up, but readiness stays
NOT_READYuntil required fields are complete and reviewed.
Client Completes Baseline Profile
- Client logs in from a validated invitation and opens the profile onboarding task.
- Client completes required sections in mobile: personal details, goals, health background, injuries, allergies, dietary restrictions, training constraints, and notification preferences.
- Client can save partial progress per section; mobile sync sends section-level updates with version metadata.
- API validates field types, unit values, required fields, and health-data acknowledgement before marking each section complete.
- When all required fields are valid, the relationship emits a profile-ready-for-review signal for the coach inbox.
Coach Reviews Profile
- Coach opens the client's profile from client list, dashboard, task inbox, or onboarding review task.
- Coach sees completion status, missing fields, recent changes, sensitive health flags, and mapped questionnaire answers.
- Coach adds private notes that are visible only to authorized coaches inside the workspace.
- Coach marks the profile
READY_FOR_PROGRAMMING,NEEDS_CLARIFICATION, orBLOCKEDwith optional client-facing follow-up tasks. - Ready status unlocks downstream assignment flows such as program builder, nutrition plan setup, and goal milestones.
Profile Updates After Onboarding
- Client or coach updates profile data when goals, injuries, allergies, diet, equipment, schedule, or medical context changes.
- API writes section version history and emits changed-field events for dependent areas.
- Material changes can reset readiness to
NEEDS_REVIEWfor that coach relationship. - Coach sees profile changes in the timeline and can acknowledge or request clarification.
Profile readiness
Profile completion is not the same as coach readiness. The client can complete all required fields, but the coach still owns the relationship-specific review decision.
Domain Model
Client profile data is client-owned. V1 baseline allows only one active or onboarding relationship per client, while requirements, readiness, and private notes remain scoped to that relationship.
Aggregates & Entities
ClientProfile: aggregate root for baseline client identity, locale, timezone, demographic fields, preferences, and section completion.ClientHealthProfile: health, medication, injury, allergy, contraindication, and clearance context. It is sensitive and permissioned separately in read models.ClientGoalProfile: primary goal, secondary goals, motivation, target date, target metrics, and status.ClientNutritionProfile: dietary pattern, restrictions, allergies, dislikes, meal schedule, cooking constraints, and supplement context.ClientTrainingProfile: experience level, equipment, availability, activity baseline, injuries, and training constraints.ProfileRequirementSet: relationship-scoped list of fields or sections required before coach review. V1 baseline requirement sets are generated from package templates instead of manual field-by-field coach configuration.ClientProfileReview: coach-scoped readiness decision and review history for the relationship.CoachPrivateNote: private note linked to client profile, relationship, event, or review decision.
Value Objects & Rules
ProfileSectionKey:PERSONAL,GOALS,HEALTH,NUTRITION,TRAINING,PREFERENCES.FieldRequirement: field key, required flag, source, visibility, validation type, and optional form-question mapping.ProfileReadinessStatus:NOT_READY,READY_FOR_REVIEW,NEEDS_CLARIFICATION,READY_FOR_PROGRAMMING,BLOCKED.ProfileSectionStatus:NOT_STARTED,IN_PROGRESS,COMPLETE,NEEDS_REVIEW.ClientGender:MALEorFEMALE.MeasurementValue: numeric value plus unit, recorded source, and captured timestamp.- A client can have only one active or onboarding
CoachClientRelationshipin Public V1; accepting another invite is blocked until the current relationship ends. - Only a client or an authorized coach can update client-visible profile fields; only coaches can write coach private notes and readiness reviews.
- Coach edits to client-visible fields are allowed with actor, timestamp, source, and previous-value audit records. They do not require client confirmation in Public V1.
- Form answers can map into profile fields, but the profile owns the current baseline value and form submissions remain historical evidence.
| Model | Owned By | Important Methods | Emits |
|---|---|---|---|
ClientProfile | CoachClientManagement | createForUser(), updateSection(), markSectionComplete(), applyFormMapping() | ClientProfileCreated, ClientProfileSectionUpdated, ClientProfileCompleted |
ProfileRequirementSet | CoachClientManagement | createForRelationship(), requireField(), isSatisfiedBy(), retire() | ProfileRequirementsAssigned, ProfileRequirementsSatisfied |
ClientProfileReview | CoachClientManagement | startReview(), requestClarification(), markReady(), block() | ClientProfileReadyForReview, ClientReadyForProgramming, ClientProfileClarificationRequested |
CoachPrivateNote | CoachClientManagement | create(), edit(), archive(), linkToEvent() | CoachPrivateNoteCreated |
Table Structure
Use normalized tables for stable, high-value profile areas and JSON fields only where the field catalog needs V1 baseline flexibility.
DDD boundary rule
profile tables store user_id, relationship_id, assigned_by_user_id, and reviewed_by_user_id as external identity references where the owned aggregate lives outside the current aggregate boundary. Validate access through relationship/user policies or read models; do not make profile aggregates navigate or mutate User or unrelated aggregate roots directly.
V1 baseline Required Profile Fields
- All clients: display name, birth date, gender, timezone, locale, preferred units, primary goal type, primary goal description, and sensitive health-data acknowledgement.
- All clients: safety screen with yes/no answers for medical conditions, medications, current injuries or limitations, allergies, contraindications, and medical clearance needs. Detail is required only when the answer is yes.
- Training package: experience level, weekly availability, training location, available equipment, and movement limitations.
- Nutrition package: dietary pattern, dietary restrictions, food allergies, disliked foods, meal schedule, and supplement context.
- Progress package: preferred measurement units and starting measurement/photo tasks when the coach package includes progress tracking.
- Coach-configurable V1 baseline fields should be optional questionnaire questions or package templates, not custom mandatory profile fields inside the profile editor.
Normalization Strategy
- Normalize immediately: identity baseline, timezone/locale/units, section statuses, package requirement sets, readiness reviews, private notes, primary goal, health safety screen, allergies, injuries, dietary restrictions, training availability, equipment, and audit/source metadata.
- Store as flexible mapped fields: coach-specific questionnaire answers, motivation details, lifestyle notes, sleep/stress context, detailed food preferences, package-specific custom prompts, and future fields that are not queried by core workflows.
- Promote a flexible field to a normalized model when two or more product surfaces query it, it gates readiness or plan publishing, or it needs permissioning beyond generic profile visibility.
| Table | Purpose | Important Columns | Constraints & Indexes |
|---|---|---|---|
client_profiles | Client-owned baseline profile and section completion state. | id, user_id, display_name, birth_date, gender, timezone, locale, unit_preferences jsonb, section_statuses jsonb, privacy_settings jsonb, created_at, updated_at, deleted_at | Unique active user_id; gender accepts MALE or FEMALE; indexes on updated_at and deleted_at; sensitive fields excluded from broad list queries. |
client_profile_health | Health context, medical flags, medications, injuries, allergies, contraindications, and clearance notes. | id, profile_id, medical_conditions jsonb, medications jsonb, injuries jsonb, allergies jsonb, contraindications jsonb, medical_clearance_status, health_acknowledgement_version, health_acknowledged_at, last_confirmed_at, updated_at | Unique profile_id; GIN indexes only on fields that need filtering; stricter read permission than general profile. |
client_profile_goals | Current goal baseline used by program, nutrition, and milestone workflows. | id, profile_id, goal_type, description, motivation, target_date, target_metrics jsonb, status, created_at, completed_at | Index (profile_id, status); only one primary active goal unless product policy changes. |
client_profile_nutrition | Dietary context used before meal plans, macros, restrictions, and nutrition coaching. | id, profile_id, dietary_pattern, dietary_restrictions jsonb, food_allergies jsonb, disliked_foods jsonb, meal_schedule jsonb, supplements jsonb, updated_at | Unique profile_id; maintain distinction between allergies and preferences. |
client_profile_training | Training context used before workout programs and exercise substitutions. | id, profile_id, experience_level, available_equipment jsonb, weekly_availability jsonb, activity_baseline, movement_limitations jsonb, preferred_training_location, updated_at | Unique profile_id; equipment values should map to the program/exercise taxonomy where possible. |
profile_requirement_sets | Relationship-scoped requirements selected by coach, package, segment, or onboarding template. | id, relationship_id, package_template_key, source_type, source_id, required_fields jsonb, status, assigned_by_user_id, assigned_at, satisfied_at, retired_at | Unique active relationship_id; index (status, assigned_at) for onboarding tasks and reminders. |
profile_field_values | Flexible mapped fields from form answers or optional package questions that are not yet promoted to normalized columns. | id, profile_id, field_key, value jsonb, source_type, source_id, visibility, version, updated_by_user_id, updated_at | Unique current (profile_id, field_key); index (field_key); append old versions to history table or audit stream. |
client_profile_reviews | Coach review decision, readiness status, and reason history for one relationship. | id, relationship_id, requirement_set_id, status, reviewed_by_user_id, reviewed_at, blockers jsonb, client_message, internal_summary, created_at | Index (relationship_id, created_at desc); latest review can be cached on relationship read model. |
coach_client_private_notes | Coach-private memory layer linked to a client relationship. | id, relationship_id, author_user_id, body, linked_event_type, linked_event_id, visibility, created_at, updated_at, archived_at | Index (relationship_id, created_at desc); not visible to clients; archive instead of hard delete in normal flows. |
Lifecycle Policy
Profiles contain sensitive client data. Treat updates, retention, anonymization, and relationship visibility deliberately.
Profile Lifecycle
ClientProfileis created during client onboarding registration or draft client creation.- Section completion is stored per profile, while requirement satisfaction is computed per relationship.
- Material updates to health, injury, allergy, goal, nutrition, or training constraints can mark the active relationship review as
NEEDS_REVIEW. - Profile values should keep latest state plus enough version history to explain coaching decisions.
- Form submissions remain historical records; profile fields store the current baseline derived from those submissions or direct edits.
Deletion And Retention
client_profiles: soft delete through account deletion; anonymize sensitive fields during privacy purge where required.- Health, nutrition, training, and flexible field rows inherit profile deletion visibility and are purged/anonymized with the profile.
profile_requirement_setsandclient_profile_reviews: relationship history; use status and timestamps rather than generic soft delete.coach_client_private_notes: archive in product flows; keep accessible to authorized coaches for 30 days after relationship end, then purge or anonymize.- Relationship-scoped health profile history and review context remain read-only for 30 days after relationship end, then lose normal coach visibility and are purged or anonymized where possible.
- Ended relationships lose coach edit access immediately and cannot update profile requirements, notes, or review state during the 30-day retention window.
Private coach notes
Never expose coach private notes through client profile endpoints, exports, or mobile offline caches. Client-visible profile data and coach-private coaching memory are separate permission domains.
API Contracts
Coach endpoints are relationship-scoped. Client endpoints are user/profile-scoped and can include requirement progress for the selected relationship.
| Endpoint | Use Case | Request | Response |
|---|---|---|---|
GET /api/v1/coach/clients/{relationshipId}/profile | Coach loads complete profile context for review or planning. | Coach bearer token, relationship id, optional include values such as notes, reviews, timeline. | Profile sections, requirement progress, latest review, readiness status, health flags, notes summary, and edit permissions. |
PUT /api/v1/coach/clients/{relationshipId}/profile-requirements | Coach applies a package template before or during onboarding. | packageTemplateKey, optional template version, due date, and onboarding task metadata. Server expands the template into requiredFields. | Requirement set, missing fields, generated or updated onboarding task references. |
POST /api/v1/coach/clients/{relationshipId}/profile-review | Coach records readiness decision after reviewing completed profile. | status, blockers, clientMessage, internalSummary, optional follow-up task definitions. | Created review, updated relationship readiness, downstream unlocks, and created tasks. |
POST /api/v1/coach/clients/{relationshipId}/private-notes | Coach adds internal context while reviewing or managing a client. | body, optional linked event, visibility within workspace. | Created note metadata and updated notes summary. |
GET /api/v1/client/profile | Client loads own profile and selected relationship requirements. | Client bearer token. V1 baseline resolves the single active or onboarding relationship; relationshipId is accepted only for idempotent deep links to that same relationship. | Client-visible profile fields, section statuses, required missing fields, and editable permissions. |
PATCH /api/v1/client/profile/sections/{sectionKey} | Client saves profile section progress from onboarding or settings. | Section payload, client-side version, optional idempotency key. | Updated section, validation errors, requirement progress, and review status changes if any. |
POST /api/v1/client/profile/sections/{sectionKey}/complete | Client marks a required profile section complete after validation. | Section version and confirmation flags such as sensitive-data acknowledgement. | Updated section status, missing required fields, next onboarding task, and ready-for-review flag. |
Key Code Snippets
These snippets are implementation sketches for the planned TypeScript monorepo. Keep controllers thin and enforce profile permissions in application services or query policies.
Drizzle schema shape
apps/api/src/coach-client-management/db/profile.schema.ts
ts
export const clientProfiles = pgTable("client_profiles", {
id: uuid("id").primaryKey().defaultRandom(),
// External identity references. Do not import/navigate User or unrelated aggregate roots here.
userId: uuid("user_id").notNull(),
displayName: text("display_name").notNull(),
birthDate: date("birth_date"),
gender: text("gender").$type<"MALE" | "FEMALE">().notNull(),
timezone: text("timezone").notNull(),
locale: text("locale").notNull().default("en"),
unitPreferences: jsonb("unit_preferences").$type<UnitPreferences>().notNull().default({}),
sectionStatuses: jsonb("section_statuses")
.$type<Record<ProfileSectionKey, ProfileSectionStatus>>()
.notNull()
.default({}),
privacySettings: jsonb("privacy_settings").$type<Record<string, unknown>>().notNull().default({}),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
deletedAt: timestamp("deleted_at", { withTimezone: true }),
}, (table) => ({
userUnique: uniqueIndex("client_profiles_user_active_uq")
.on(table.userId)
.where(sql`${table.deletedAt} is null`),
updatedIdx: index("client_profiles_updated_idx").on(table.updatedAt),
}));
export const profileRequirementSets = pgTable("profile_requirement_sets", {
id: uuid("id").primaryKey().defaultRandom(),
relationshipId: uuid("relationship_id").notNull(),
packageTemplateKey: text("package_template_key").notNull().default("BASELINE"),
sourceType: text("source_type").notNull(),
sourceId: uuid("source_id"),
requiredFields: jsonb("required_fields").$type<FieldRequirement[]>().notNull(),
status: text("status").notNull().default("ACTIVE"),
assignedByUserId: uuid("assigned_by_user_id").notNull(),
assignedAt: timestamp("assigned_at", { withTimezone: true }).notNull().defaultNow(),
satisfiedAt: timestamp("satisfied_at", { withTimezone: true }),
retiredAt: timestamp("retired_at", { withTimezone: true }),
}, (table) => ({
activeRelationshipUnique: uniqueIndex("profile_requirement_sets_relationship_active_uq")
.on(table.relationshipId)
.where(sql`${table.retiredAt} is null`),
statusAssignedIdx: index("profile_requirement_sets_status_assigned_idx").on(table.status, table.assignedAt),
}));
export const clientProfileReviews = pgTable("client_profile_reviews", {
id: uuid("id").primaryKey().defaultRandom(),
relationshipId: uuid("relationship_id").notNull(),
requirementSetId: uuid("requirement_set_id").references(() => profileRequirementSets.id),
status: text("status").notNull(),
reviewedByUserId: uuid("reviewed_by_user_id").notNull(),
reviewedAt: timestamp("reviewed_at", { withTimezone: true }).notNull().defaultNow(),
blockers: jsonb("blockers").$type<ProfileReviewBlocker[]>().notNull().default([]),
clientMessage: text("client_message"),
internalSummary: text("internal_summary"),
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
}, (table) => ({
relationshipReviewIdx: index("client_profile_reviews_relationship_created_idx")
.on(table.relationshipId, table.createdAt),
}));Section update use case
apps/api/src/coach-client-management/use-cases/update-profile-section.ts
ts
@Injectable()
export class UpdateClientProfileSectionUseCase {
constructor(
private readonly profiles: ClientProfileRepository,
private readonly requirements: ProfileRequirementRepository,
private readonly relationships: CoachClientRelationshipRepository,
private readonly policy: ClientProfilePolicy,
private readonly tx: TransactionRunner,
private readonly clock: Clock,
) {}
async execute(input: UpdateProfileSectionInput, actor: Actor): Promise<ProfileSectionDto> {
return this.tx.run(async () => {
const profile = await this.profiles.lockById(input.profileId);
await this.policy.assertCanUpdateSection(actor, profile, input.sectionKey);
profile.updateSection({
sectionKey: input.sectionKey,
values: input.values,
updatedByUserId: actor.userId,
now: this.clock.now(),
});
const relationship = await this.relationships.findActiveOrOnboardingForClient(profile.userId);
if (relationship) {
const requirementSet = await this.requirements.findActiveByRelationship(relationship.id);
if (requirementSet?.isSatisfiedBy(profile)) requirementSet.markSatisfied(this.clock.now());
if (profile.sectionRequiresCoachReview(input.sectionKey)) relationship.markProfileNeedsReview();
}
await this.profiles.save(profile);
await this.profiles.saveEventsToOutbox(profile);
return ProfileSectionDto.from(profile, input.sectionKey);
});
}
}Coach review transaction
apps/api/src/coach-client-management/use-cases/review-client-profile.ts
ts
@Injectable()
export class ReviewClientProfileUseCase {
constructor(
private readonly profiles: ClientProfileRepository,
private readonly requirements: ProfileRequirementRepository,
private readonly relationships: CoachClientRelationshipRepository,
private readonly reviews: ClientProfileReviewRepository,
private readonly tasks: CoachTaskService,
private readonly policy: ClientProfilePolicy,
private readonly tx: TransactionRunner,
) {}
async execute(input: ReviewClientProfileInput, actor: Actor): Promise<ClientProfileReviewDto> {
return this.tx.run(async () => {
const relationship = await this.relationships.lockById(input.relationshipId);
await this.policy.assertCanReview(actor, relationship);
const profile = await this.profiles.findByUserId(relationship.clientUserId);
const requirements = await this.requirements.findActiveByRelationship(relationship.id);
if (input.status === "READY_FOR_PROGRAMMING") requirements.assertSatisfiedBy(profile);
const review = ClientProfileReview.create({
relationshipId: relationship.id,
requirementSetId: requirements.id,
status: input.status,
reviewedByUserId: actor.userId,
blockers: input.blockers,
clientMessage: input.clientMessage,
internalSummary: input.internalSummary,
});
relationship.applyProfileReview(review);
await this.reviews.save(review);
await this.relationships.save(relationship);
await this.tasks.syncProfileReviewFollowUps(relationship, review);
return ClientProfileReviewDto.from(review, relationship);
});
}
}Read policy boundary
apps/api/src/coach-client-management/policies/client-profile.policy.ts
ts
export class ClientProfilePolicy {
assertCanReadCoachProfile(actor: Actor, relationship: CoachClientRelationship): void {
actor.requireRole("COACH");
actor.requireWorkspaceMembership(relationship.workspaceId, ["OWNER", "COACH"]);
relationship.assertVisibleToCoach(actor.userId);
}
assertCanReadPrivateNotes(actor: Actor, relationship: CoachClientRelationship): void {
this.assertCanReadCoachProfile(actor, relationship);
actor.requirePermission("client.private_notes.read");
}
assertCanUpdateSection(actor: Actor, profile: ClientProfile, sectionKey: ProfileSectionKey): void {
if (actor.isClient(profile.userId)) return;
actor.requireRole("COACH");
actor.requirePermission(sectionKey === "HEALTH" ? "client.health_profile.write" : "client.profile.write");
}
filterClientVisibleFields(profile: ClientProfileReadModel): ClientProfileReadModel {
return {
...profile,
privateNotes: undefined,
internalReviewSummary: undefined,
coachOnlyFlags: undefined,
};
}
}Events & Background Jobs
Profile changes feed onboarding progress, coach inbox tasks, programming readiness, nutrition setup, and audit trails.
Domain / Integration Events
ClientProfileCreated: emitted after client account or draft client creation.ProfileRequirementsAssigned: emitted when coach/package/template selects required fields.ClientProfileSectionUpdated: emitted for section-level changes and downstream review resets.ProfileRequirementsSatisfied: emitted when required fields are complete.ClientProfileReadyForReview: emitted to coach inbox after completion.ClientReadyForProgramming: emitted when coach marks profile ready.ClientProfileClarificationRequested: emitted when coach requests missing or corrected information.
Jobs
- Create or update onboarding tasks when profile requirements are assigned.
- Send reminders for incomplete required profile sections.
- Create coach inbox task when required profile fields are complete.
- Reset dependent plan/nutrition review flags when health, allergy, injury, equipment, or goal data changes.
- Run profile stale-data prompts for long-running clients to reconfirm health, allergies, goals, and equipment.
- Purge or anonymize private notes and relationship-scoped health history after the 30-day ended-relationship retention window.
- Anonymize or purge sensitive profile values during account deletion retention workflows.
Permissions & Access Rules
The profile is sensitive because it includes health, diet, body, goal, and coaching notes. Access must be relationship-aware.
- Clients can read and edit their own client-visible profile fields while their user account is active.
- V1 baseline allows one active or onboarding
CoachClientRelationshipper client. A second coach invitation is rejected until the current relationship ends. - Coaches can read client profiles only through an active or onboarding
CoachClientRelationshipin their workspace, plus the 30-day read-only retention window after relationship end. - Coach edits to client-visible fields must be attributed and shown in audit/change history. Client confirmation is not required in Public V1.
- Coach private notes are never returned to client endpoints, exports, or offline client caches.
- Health sections require explicit permissions such as
client.health_profile.readandclient.health_profile.write, and saving health/medical fields requires the current health-data acknowledgement version. - Health-data acknowledgement is required before saving medical conditions, medications, injuries, allergies, contraindications, pregnancy/postpartum status, eating-disorder history, or medical clearance notes. The copy should state that CoachMe stores this context for coaching, it is not medical diagnosis or emergency care, it may be visible to authorized workspace coaches, and the client can update or request deletion under policy.
- Ended relationships are read-only for 30 days, then hidden from normal coach access after retention cleanup. They cannot update profile requirements, notes, or readiness.
- Profile list endpoints must return summaries only and exclude sensitive health/nutrition details unless the query explicitly asks and permission is proven.
Web And Mobile Client Behavior
Each surface sees the same domain state but optimizes the workflow for its job: coach review, mobile coaching, or client completion.
Coach Web
- Dense profile view with section tabs, completion state, health flags, goal summary, timeline, notes, and review controls.
- Client list shows readiness, missing profile fields, last updated date, and review task state.
- Program and nutrition assignment flows check readiness before allowing publish.
- Support print/export only for client-visible profile data unless admin policy allows a privileged export.
Coach Mobile
- Read optimized profile summary with high-signal fields: goals, injuries, allergies, restrictions, equipment, and recent notes.
- Allow quick private notes, clarification request, and readiness decision from inbox review.
- Use same permission model as web; mobile should not cache private notes without secure storage policy.
- Support push/deep links from profile-ready-for-review tasks.
Client Mobile
- Onboarding profile task is section-based, resumable, and usable with partial saves.
- Validate fields locally for required, unit, date, and selection rules before syncing to API.
- Cache drafts offline only for the authenticated client and encrypt local sensitive fields where platform support allows.
- Show coach clarification requests as actionable tasks and preserve previous answers for editing.
Test Scenarios & V1 Baseline Decisions
Profile tests should cover relationship scoping, package-generated required fields, sensitive field permissions, review transitions, retention cleanup, and downstream readiness gates.
Required Tests
- Domain: requirement set is satisfied only when all required fields are present and valid.
- Domain: coach cannot mark ready for programming when required fields are missing.
- Domain: material profile updates move relationship readiness to
NEEDS_REVIEW. - API integration: client can save partial section progress and later complete the same section idempotently.
- API integration: coach profile read requires active/onboarding relationship in the workspace.
- API integration: accepting a second invite fails while the client has an active or onboarding relationship.
- Permission: client endpoints never include private notes or internal review summaries.
- Permission: coach without health permission cannot read or write health profile details.
- Permission: health/medical field save fails without the current health-data acknowledgement version.
- Contract: questionnaire mapped answer updates profile current value while preserving form submission history.
- Contract: coach edits to client-visible fields create audit entries and do not create client confirmation tasks.
- Retention: ended relationships block writes immediately and purge or anonymize private notes and relationship-scoped health history after 30 days.
- E2E: invite client, complete profile, coach reviews, marks ready, and program assignment becomes available.
- Security: ended relationship cannot mutate profile requirements, notes, or review status.
Resolved V1 Baseline Decisions
- Client relationship model: one client can have one active or onboarding coach relationship in Public V1; no relationship-specific profile copies or concurrent-coach visibility controls are needed.
- Retention after relationship end: private notes and relationship-scoped health profile history are retained read-only for 30 days, then purged or anonymized.
- Coach edits: coach edits to client-visible fields require audit attribution only; no client confirmation task is needed.
- Required fields: use a small mandatory baseline for every client and add training, nutrition, or progress field groups through package templates.
- Health consent: require explicit health-data acknowledgement before saving sensitive health/medical fields, and store acknowledgement version and timestamp.
- Field storage: normalize fields needed by core workflows, permissions, search, readiness, and plan validation; keep coach-specific or experimental questionnaire mappings in
profile_field_values.