| name | skill-api |
| description | Skill to create new features in the API that has context on our data model and business logic. |
MC-Review API skill
Reference for any agent building or modifying contract/rate features in the services/app-api/ portion of the managed-care-review codebase. The supporting files in references/ describe what the data layer actually does — including the non-obvious dynamics that aren't visible from reading any single file.
When to read these references
Read the relevant reference files when the task involves:
- Building or modifying a contract/rate feature (any new mutation, status transition, history view, etc.)
- Building or modifying question/response store or resolver behavior in
services/app-api/src/postgres/questionResponse/ or services/app-api/src/resolvers/questionResponse/
- Reasoning about why a status, package snapshot, or relationship looks the way it does
- Touching the
services/app-api/src/postgres/contractAndRates/, services/app-api/src/postgres/questionResponse/, services/app-api/src/postgres/prismaHelpers.ts, services/app-api/src/domain-models/contractAndRates/, or services/app-api/src/resolvers/{contract,rate,questionResponse}/ directories
- Following code that interacts with
ContractTable, RateTable, ContractQuestion, RateQuestion, UpdateInfoTable, SubmissionPackageJoinTable, or DraftRateJoinTable
If the task only involves UI/frontend without touching the data layer, you probably don't need these references.
Reference map — pick by task
| If the task touches… | Read |
|---|
| Anything contract/rate-shaped (start here) | references/01-storage-schema.md + references/02-domain-types.md |
Reading data via findContractWithHistory / findRateWithHistory; building a new query path; understanding packageSubmissions shape | references/03-parse-pipeline.md |
| Creating a new draft contract or rate; editing form data on a draft; reconciling rates on a draft | references/04-insert-and-update.md |
| Submit, unlock, or resubmit (any state-flip operation) | references/05-submit-unlock-resubmit.md |
Adding or changing questionResponse store/resolver logic; reasoning about lock-vs-stale-state behavior on ContractQuestion / RateQuestion | Read this SKILL.md for the row-lock rules, then inspect services/app-api/src/postgres/questionResponse/ directly because there is no dedicated reference file yet |
| Anything involving rates that span multiple contracts (linking, parent reassignment, withdraw rate fallout) | references/06-linked-rates.md |
| Withdraw contract specifically | references/07-withdraw.md |
Working out what status a contract or rate ends up in; computing parent contract; resolving cause on packageSubmissions; recursion in domain types | references/08-derived-state.md |
| Undo unlock, reversed revisions, active-draft rules, linked-rate cleanup | references/09-undo-unlock.md |
| Contract/rate data overrides, override mutations, parser merge behavior, adding new overridable fields | references/10-revision-overrides.md + docs/technical-design/contract-and-rate-data-override.md |
For broad multi-area features (e.g. a new "undo unlock contract" mutation): read 01, 02, 05, and 08 first; pull in 06 and 07 if rates cross contract boundaries.
Cheat sheet — facts to keep in mind
- Two-tier pattern: parent table (identity) + revision table (form data). Form data lives only on revisions; parents are stable anchors.
- Submit stamps an existing draft revision in place. Unlock CREATES a new revision row. Both flip status by setting
submitInfoID / unlockInfoID on a row.
- Status is never stored. It's derived in the parser from the latest revision's
submitInfo/unlockInfo pair, plus the latest *ActionTable row, plus a few special-case rules.
- There are two different kinds of GraphQL resolvers in this codebase.
- Top-level Query/Mutation resolvers are operation entrypoints such as
submitContract, unlockContract, fetchRate, createContractQuestion. These handle auth, user-input validation, orchestration, store writes/reads, and error formatting for client-initiated operations.
- Schema object-field resolvers are the per-type resolver objects registered under keys like
Rate, Contract, RateRevision, ContractQuestion, GenericDocument, DocumentZipPackage in services/app-api/src/resolvers/configureResolvers.ts. These run when GraphQL resolves selected fields on already-fetched objects. They usually compute derived fields (webURL, state, initiallySubmittedAt, packageSubmissions, downloadURL, round) or do secondary lookups, but they are not top-level workflow entrypoints.
- Treat these categories differently when changing behavior: operation resolvers are where business-action eligibility, authorization, and mutation orchestration usually belong; object-field resolvers are where type hydration/derivation logic belongs.
- Parent contract is never stored. It's derived per rate as "the contract whose latest submission stamped this rate's latest submitted revision."
- One
UpdateInfoTable event row per submit-or-unlock; shared across the contract and every child rate touched in that transaction. This is what lets the parser later detect "these were submitted/unlocked together."
SubmissionPackageJoinTable is the immutable snapshot of contract↔rate at submit time, joining specific contract revision + specific rate revision + position. DraftRateJoinTable is the working set on a contract that's currently in draft, joining parent tables only.
- Form-data writes via
updateDraftContract are full replaces, not merges. Omitted fields are nulled (nullify) or emptied (emptify). Child collections (documents, contacts) are delete-and-recreate, so row IDs are not stable across updates.
- Resolver-called store function writes to
ContractTable, RateTable, ContractQuestion, or RateQuestion should default to runTransactionWithRowLock. Use services/app-api/src/postgres/prismaHelpers.ts and pass the table + row id so concurrent writes to the same record serialize before the store performs additional reads or writes.
- After acquiring a row lock, re-check write preconditions inside the transaction. Resolver-time validation can be stale by the time the lock is acquired; the store should validate current state again before applying writes.
- Question-response writes follow the same rule as contract/rate state changes. If a resolver-called store function writes a
ContractQuestion or RateQuestion row or appends related responses based on current question state, default to runTransactionWithRowLock and validate deleted/current status after the lock is acquired.
- Undo unlock is revision history, not a new submission event. Read
09-undo-unlock.md if you touch undoUnlockInfo, active-draft logic, or linked-rate cleanup.
- Contract/rate data overrides are append-only correction events on submitted data. Read
10-revision-overrides.md and docs/technical-design/contract-and-rate-data-override.md before touching override mutations, override tables, parser merge behavior, document override lookup, or unlock/submit behavior for overridden data.
- Override operation columns carry intent; value columns carry payload. Do not infer override behavior from null/undefined values.
OVERRIDE applies payload, CLEAR_OVERRIDE clears scalar field override state, and nullable op columns mean no instruction.
- Override reads apply in the postgres-to-domain parser. Most effective contract/rate data should be merged in parse helpers, not ad hoc in GraphQL object-field resolvers. Some parent-level fields may still need field resolver handling when the response shape does not use full parser output.
- Document overrides are array-item operations, not full array replacement. Use
documentSha256 as the sparse merge key, use documentID to disambiguate duplicate base docs, and remember override-added documents expose the override row id as GenericDocument.id.
- Deprecated — do not include in new designs:
ContractTable.sharedRateRevisions / RateRevisionTable.contractsWithSharedRateRevision (the SharedRateRevisions M:N) — not marked deprecated in the schema source, but confirmed deprecated 2026-05-04.
HealthPlanPackageTable / HealthPlanRevisionTable (proto legacy) — marked deprecated Boolean @default(true).
- A
Without... Zod schema (e.g. contractWithoutDraftRatesSchema) is the one-level recursion break. It exists so a contract can embed rates and a rate can embed contracts without infinite recursion. The include shape mirrors this with includeContractWithoutDraftRates / includeRateWithoutDraftContracts.
DRAFT_PARENT_PLACEHOLDER ('DRAFT_PARENT_REPLACE_ME'): a never-submitted rate has no real parent, so the parser returns this placeholder; the outer parser patches it from draftContracts[0].id.
- Schema typo to know about:
ContractRevisionTable.relatedSubmisions (one s) vs RateRevisionTable.relatedSubmissions (correct). Both are valid relation names; code uses each side's spelling.
Submission history and freshness
Use this model when changing lastActionDate, submissionHistoryHelpers, fetchSubmissionHistory, submit/unlock/withdraw flows, override flows, or Q&A writes.
Core rule: lastActionDate should move when submitted-visible data changes for CMS users, or when a visible review, Q&A, or status action happens. Draft-only form edits should not move CMS-facing freshness unless the action itself is visible, such as an unlock.
For contracts, submitted-visible data means the latest submitted contract package: contract form data plus submitted rate data attached to that package. For rates, submitted-visible data means the latest submitted rate revision plus submitted contract relationship changes, rate overrides, rate Q&A, and rate review/status actions.
Write lastActionDate at the lowest-level store/transaction function that writes the user-visible action row or submission event to the database. Do not rely on a resolver or broad orchestration wrapper to recalculate it later. Examples: submit freshness belongs in submitContractAndOrRates, review freshness belongs where the review action row is appended, override freshness belongs where the override row is created, and Q&A freshness belongs where the question/response/action row is created.
When one workflow writes multiple actions in one transaction, ordering matters: intermediate submit/unlock writes may happen first, but the final visible action should overwrite lastActionDate. For example, withdraw and undo-withdraw flows can submit/unlock first, then write a review/status action that becomes the stored freshness date.
Contract freshness
Contract lastActionDate should update for:
- Direct contract submit/resubmit.
- Contract unlock.
- Contract review actions such as
UNDER_REVIEW, NOT_SUBJECT_TO_REVIEW, MARK_AS_APPROVED, WITHDRAW, reverse approve, and undo-withdraw restored review actions.
- Contract data overrides.
- Contract Q&A create/response/delete/restore actions.
- Already-linked rate data updates when the rate's parent contract resubmits the rate.
- Linked or child rate overrides while the rate is attached in submitted package history.
- Linked or child rate Q&A while the rate is attached in submitted package history.
- Rate withdraw/undo-withdraw when it changes the contract's submitted package or review state.
Contract lastActionDate should not update for:
- Draft-only form edits.
- Draft-only rate links/unlinks.
- Rate actions before the rate was attached to the contract in submitted history.
- Rate actions after the rate was removed from the contract's submitted package.
- Rate link/unlink as a separate contract event, because a submitted link/unlink is represented by the contract's own submit/resubmit history.
Rate freshness
Rate lastActionDate should update for:
- Rate submission through parent contract submit/resubmit.
- Rate unlock through parent contract unlock.
- Rate review/status actions such as
UNDER_REVIEW, WITHDRAW, undo-withdraw restored actions, and future rate review actions if added.
- Rate data overrides.
- Rate Q&A create/response/delete/restore actions.
- Rate link/unlink through contract submit/resubmit, because that changes the rate's submitted contract relationships even if the rate revision itself did not change.
- Withdraw contract when child rates are withdrawn.
- Withdraw rate and undo-withdraw rate.
Rate lastActionDate should not update for:
- A linked contract resubmitting without changing this rate relationship or rate data.
- Draft-only rate links/unlinks that have not been submitted.
History builders
buildContractSubmissionHistory returns contract-level submitted history:
- Includes
CONTRACT_SUBMISSION, UNLOCK, LINKED_RATE_UPDATE, contract OVERRIDE, and contract review actions.
- Skips
RATE_LINK and RATE_UNLINK by design. From the contract perspective, submitted link/unlink changes are captured by the contract's own submit/resubmit. Separate link/unlink entries would duplicate contract submission history and are brittle for draft-only relationship changes.
buildRateSubmissionHistory returns rate-level submitted history:
- Includes
RATE_SUBMISSION, UNLOCK, RATE_LINK, RATE_UNLINK, rate OVERRIDE, and rate review actions.
- Skips already-related contract submissions that do not change rate data or the submitted contract relationship for that rate.
buildQuestionResponseHistory returns scoped Q&A actions:
- Contract scope uses
CONTRACT_QUESTION, CONTRACT_QUESTION_RESPONSE, and contract question/response delete/restore action types.
- Rate scope uses
RATE_QUESTION, RATE_QUESTION_RESPONSE, and rate question/response delete/restore action types.
- Cascade delete events should be skipped; the direct parent question/response action is the user-visible event.
buildCompleteHistory merges already-built histories and sorts newest-first. If two actions have the same JS millisecond, actionTypeSortRank breaks ties so later lifecycle actions such as overrides or review/status actions sort above submit, and submit sorts above unlock. Keep this tie-breaker in mind when adding action types that can share a DB transaction timestamp.
History builders are best-effort readers over parsed domain data. They should not block fetching a contract/rate because history shape is imperfect; for example, ambiguous missing previous package data is logged/skipped rather than throwing.
Full submission history query
fetchSubmissionHistory is the heavier explicit history query. The resolver should stay thin: check OAuth/read permissions, fetch the contract enough to enforce state/CMS/Admin access, then call findSubmissionHistoryByContractID for the assembled history and map the result to GraphQL. Keep the expensive history assembly in the store layer so resolver tests exercise the same data-building path other callers can reuse.
findSubmissionHistoryByContractID merges contract submission history, contract Q&A history, and selected rate-owned history filtered to windows where the rate was attached to the contract in submitted package history. This windowing matters because a linked rate can be linked, delinked, and relinked; rate actions only belong in contract history when CMS users could see that rate through the contract's latest submitted package.
For contract history, derive attached-rate windows from the contract's own packageSubmissions, not from rate-wide package history. The contract package timeline is the source of truth for when a given rate was submitted as part of that contract. Rate package history can include parent submits and other linked contracts that are not relationship changes for this contract.
Do not merge buildRateSubmissionHistory wholesale into contract fetchSubmissionHistory. Contract history should include attached-window-filtered rate overrides and rate Q&A because those can change CMS-visible submitted data without a contract submit. It should not include raw rate RATE_SUBMISSION, RATE_LINK, RATE_UNLINK, UNLOCK, or rate review/status entries from the rate builder; those are either represented by the contract package timeline or are not contract-scoped enough for this view.
Important edge cases:
- If Contract B links to Rate R owned by Contract A, and Contract A resubmits Rate R while B already has R in submitted history, then B's submitted-visible rate data changed. B history should get
LINKED_RATE_UPDATE, and B lastActionDate should update to that parent submit timestamp.
- If Contract B is unlocked when Rate R is overridden, the override can still affect B's submitted-visible data because CMS users see the latest submitted package, not B's draft. The linked rate override should move B
lastActionDate and appear in explicit submission history while R is attached.
Related contract freshness helpers
updateRelatedContractsLastActionDateByRateID resolves contracts from the rate's latest submitted package snapshot. It is appropriate for rate-visible changes that happen outside a contract submit, such as rate overrides and rate Q&A. It intentionally ignores draft-only links and previously removed links. It throws if no currently related submitted contracts exist, so callers should only use it when at least one related contract is expected.
submitContractAndOrRates also updates related contracts when a submitted rate was already attached to those contracts in their previous submitted package. This keeps lastActionDate aligned with the LINKED_RATE_UPDATE history entry. Group these writes with contract freshness writes; rate freshness writes are separate and should only update rates whose own rate history changed.
Display and indexing
lastActionDate is the stored CMS/Admin freshness date. CMS/Admin users do not see draft-only edits, so lastUpdatedForDisplay uses lastActionDate when present and falls back to updatedAt. State users can see draft changes, so display/index formatting considers the latest of lastActionDate and draftRevision.updatedAt.
indexContracts should use the stored lastActionDate for CMS/Admin filtering/sorting when the use-stored-contract-action-dates flag is on rather than recalculating full history for every indexed contract. The legacy flag-off updatedWithin path uses calculated display freshness and is incomplete compared with lastActionDate; keep compatibility fixes minimal and avoid treating it as canonical submission history.
Implementation checklist for new actions
When adding a new action, status transition, override path, Q&A path, submit-like workflow, or relationship change, explicitly answer these questions in code review:
-
Is this action visible to CMS users?
- If it changes submitted-visible contract/rate data, review/status state, Q&A, or override state, it probably belongs in history and should move
lastActionDate.
- If it is draft-only form data or draft-only relationship state, it generally should not move CMS-facing
lastActionDate.
-
Which record's freshness moves?
- Contract action/data changes move
ContractTable.lastActionDate.
- Rate action/data changes move
RateTable.lastActionDate.
- Rate-visible changes can also move related contract
lastActionDate when the rate is attached to those contracts in submitted package history.
- Contract relationship changes can move rate
lastActionDate when the submitted relationship for the rate changes.
-
Where should the write happen?
- Write
lastActionDate in the lowest-level store/transaction function that writes the action row or submission event.
- Use the DB-created action timestamp (
createdAt / updatedAt) instead of creating a separate timestamp when possible.
- If the same workflow writes several actions, make the final visible action write last so it becomes the stored freshness date.
-
Which history builder should emit the action?
- Contract-only submitted-visible actions belong in
buildContractSubmissionHistory.
- Rate-only submitted-visible actions belong in
buildRateSubmissionHistory.
- Q&A actions belong in
buildQuestionResponseHistory.
- Cross-rate contract history that is too expensive or relationship-window dependent belongs in
fetchSubmissionHistory, not in a lightweight field resolver.
-
Does fetchSubmissionHistory need relationship-window filtering?
- If the action comes from a rate but is shown in contract history, include it only during windows where the rate was attached to the contract in submitted package history.
- For contract history, only rate overrides and rate Q&A are currently merged from rate-owned history; do not add raw rate submit/link/unlink/unlock/review entries without revisiting the contract-scoped semantics.
- Do not include rate actions before a link, during a delink gap, after unlink/withdraw removal, or from draft-only links.
-
Do GraphQL enums/types need updates?
- New history action types must be added to the app GraphQL schema and generated types.
- Keep action names specific enough to distinguish contract-scoped and rate-scoped history when both can appear in a combined history response.
-
What tests prove freshness and history stay aligned?
- Add resolver or store tests that assert the affected table's
lastActionDate equals the timestamp of the newest relevant history entry.
- Include negative tests for actions that should not appear or should not move freshness, especially draft-only changes and out-of-window linked rate actions.
- For long workflows, add checkpoint assertions after high-risk intermediate actions, not only at the end.
- Prefer creating real submissions/actions through existing resolver helpers over hand-crafting domain history data when testing end-to-end behavior.
-
Does indexing/display behavior still hold?
- If a new action should affect CMS/Admin freshness, confirm stored
lastActionDate is enough for indexContracts and lastUpdatedForDisplay.
- If state users can see draft-side effects, confirm display code still considers draft revision updates separately from stored CMS freshness.
Authorization patterns at a glance
| Mutation | Who can do it | Source of rule |
|---|
insertDraftContract / insertDraftRate / updateDraftContract / updateDraftContractRates | State user matching the contract's stateCode | Resolver |
submitContract (initial submit + resubmit) | State user matching the contract's stateCode | Resolver |
unlockContract | CMS user (hasCMSPermissions) | Resolver |
unlockRate | CMS user (hasCMSPermissions), inactive standalone-rate path | Resolver |
withdrawContract / withdrawRate | CMS user | Resolver |
overrideContractData / overrideRateData | Admin user only | Resolver + store status checks |
Edit a rate's form data via UPDATE | Only the rate's parent contract | Postgres + resolver enforce parentContractID === contract.id |
Link to a rate via LINK | Any contract (target must not be DRAFT or WITHDRAWN) | Resolver |
OAuth canWrite (or canOauthWrite) is required on every write. State-vs-CMS checks are layered on top.
Key file map
| Concern | Path |
|---|
| Schema | services/app-api/prisma/schema.prisma |
| Domain model index | services/app-api/src/domain-models/contractAndRates/index.ts |
| Base Zod shapes | services/app-api/src/domain-models/contractAndRates/baseContractRateTypes.ts |
| Full Zod shapes | contractTypes.ts, rateTypes.ts (same dir) |
packageSubmissions schemas | packageSubmissions.ts (same dir) |
| Revision schemas | revisionTypes.ts (same dir) |
| Status schemas | statusType.ts (same dir) |
UpdateInfoType | updateInfoType.ts (same dir) |
| Submission history domain types | services/app-api/src/domain-models/contractAndRates/submissionHistoryTypes.ts |
| Find entry points | services/app-api/src/postgres/contractAndRates/findContractWithHistory.ts, findRateWithHistory.ts |
| Full include shapes | prismaFullContractRateHelpers.ts (same dir) |
| Without-draft includes | prismaSubmittedContractHelpers.ts, prismaSubmittedRateHelpers.ts |
| Parsers | parseContractWithHistory.ts, parseRateWithHistory.ts |
| Override write paths | services/app-api/src/resolvers/contract/overrideContractData.ts, services/app-api/src/resolvers/rate/overrideRateData.ts, services/app-api/src/postgres/contractAndRates/overrideContractData.ts, services/app-api/src/postgres/contractAndRates/overrideRateData.ts |
| Override merge and validation helpers | services/app-api/src/postgres/prismaOverrideMergeHelpers.ts |
| Status helpers, parent-id, formData converters | prismaSharedContractRateHelpers.ts |
| Insert (create new) | insertContract.ts, insertRate.ts |
| Update draft form data | updateDraftContract.ts, updateDraftRate.ts, updateDraftContractRates.ts, updateDraftContractWithRates.ts |
| Domain↔Prisma form-data adaptors | prismaContractRateAdaptors.ts |
nullify / emptify helpers | services/app-api/src/postgres/prismaDomainAdaptors.ts |
| Submit resolver | services/app-api/src/resolvers/contract/submitContract.ts |
| Submit postgres outer | services/app-api/src/postgres/contractAndRates/submitContract.ts |
| Submit core engine | submitContractAndOrRates.ts (same dir) |
| Submission history builders | services/app-api/src/postgres/submissionHistoryHelpers.ts |
| Full submission history store reader | services/app-api/src/postgres/contractAndRates/findSubmissionHistoryByContractID.ts |
| Full submission history resolver | services/app-api/src/resolvers/contract/fetchSubmissionHistory.ts |
| lastActionDate helpers | services/app-api/src/postgres/updateLastActionDateHelpers.ts |
| Resolver registration map (top-level + object-field resolvers) | services/app-api/src/resolvers/configureResolvers.ts |
| Unlock resolver | services/app-api/src/resolvers/contract/unlockContract.ts |
| Unlock postgres (contract) | services/app-api/src/postgres/contractAndRates/unlockContract.ts |
| Unlock postgres (rate engine) | unlockRate.ts (same dir) |
| Standalone unlock rate resolver | services/app-api/src/resolvers/rate/unlockRate.ts (standalone-rate scaffold; not expected to match unlockContract parity while rates submit with contracts only) |
| Withdraw rate resolver | services/app-api/src/resolvers/rate/withdrawRate.ts |
| Withdraw rate postgres | services/app-api/src/postgres/contractAndRates/withdrawRate.ts |
| Withdraw contract resolver | services/app-api/src/resolvers/contract/withdrawContract.ts |
| Withdraw contract postgres | services/app-api/src/postgres/contractAndRates/withdrawContract.ts |
| Parent reassignment | reassignParentContract.ts (same dir) |
| Parent picker / new-parent selection | getNewParentContract in prismaSharedContractRateHelpers.ts |
Cause derivation (*WithCause) | services/app-api/src/resolvers/contract/contractResolver.ts, services/app-api/src/resolvers/rate/rateResolver.ts |
| Other status mutations | sibling files in same dir (approveContract.ts, reverseApproveContract.ts, undoWithdrawContract.ts, etc.) |
Related codebase docs
The repo's docs/ directory has broader architecture and conventions docs that complement this skill. Most relevant for API work:
Architecture and conventions
docs/architectural-decision-records/023-seperate-contract-rates-tables-postgres.md — the ADR that established the two-tier contract/rate schema this skill describes
docs/technical-design/contract-and-rate-data-override.md — canonical design for contract/rate override storage, operation semantics, read-time merge behavior, write validation, document overrides, and submission lifecycle behavior
docs/technical-design/graphql-resovler-design.md — resolver layer conventions (error formatting, structure, audit trails)
docs/technical-design/dependency-injection-pattern.md — how the Store is injected into resolvers
docs/architectural-decision-records/011-typescript-error-handling.md + 012-custom-error-types.md — the result/error pattern used throughout the postgres helpers
Adding a feature
docs/technical-design/creating-and-testing-endpoints.md — end-to-end checklist for a new resolver
docs/technical-design/error-handling.md — errorUtils.ts, GraphQLError codes, common error shapes
docs/technical-design/testing-approach.md — integration vs. unit testing conventions
docs/technical-design/howto-migrations.md — Prisma migration workflow
Naming
docs/technical-design/contract-rate-types-naming.md — contract/rate type naming (heads-up: file truncates mid-section in the current repo state)
docs/technical-design/naming-conventions.md — broader naming guide
Adjacent topics
- Auth:
docs/architectural-decision-records/026-oauth-machine-to-machine-authentication.md, docs/technical-design/oauth.md, docs/technical-design/api-jwt-security.md, docs/technical-design/third_party_api_access.md
- Feature flags:
docs/architectural-decision-records/016-launchdarkly-server-side-flags.md, docs/technical-design/launch-darkly-implementation.md, docs/technical-design/launch-darkly-testing-approach.md (heads-up: Jest references are stale; project uses Vitest)
- File upload:
docs/technical-design/file-upload.md
- Observability:
docs/architectural-decision-records/017-application-observability.md, docs/technical-design/monitoring.md
- Stack overview:
docs/Technologies.md (heads-up: may say React 18; project is on React 19 as of 2026-05)
- Local dev:
docs/Configuration.md, docs/technical-design/howto-access-deployment-database.md, docs/technical-design/howto-update-state-programs.md
- Schema reference:
docs/technical-design/database-diagram.md
Historical / superseded — cite for context only
- ADRs 004, 008, 009, 018 — early proto-based
HealthPlanPackage design, since superseded by the contract/rate tables this skill describes
- ADR 002 — unmarked but superseded by ADR 031
- ADR 024 — labeled "Proposed" though the work is in production
- ADR 025 — references the Serverless framework; the project has migrated to CDK
How to apply this skill
- Start at the schema (reference 01) to confirm what's actually stored vs derived. Most "states" are derived in the parse layer.
- Read the parser (reference 03) to understand how the change will surface in the domain output. A status change usually means flipping
submitInfo/unlockInfo on the latest revision, adding a new revision, or appending an action row.
- Decide whether the work belongs in an operation resolver or an object-field resolver. Check
services/app-api/src/resolvers/configureResolvers.ts first if you're unsure which kind you're looking at.
- Query/Mutation resolver: operation boundary, auth, mutation orchestration, user-facing error shaping.
- Object-field resolver: per-type field hydration/derivation after the parent object already exists.
- Trace the relevant mutation (references 04–07) end-to-end if the task is an operation change. The resolver does authorization, validation, and formatting; the postgres layer does the actual DB work — they often have important guards in different places.
- If the resolver calls a store function that writes a row in
ContractTable, RateTable, ContractQuestion, or RateQuestion, default to runTransactionWithRowLock. Only skip it when last-write-wins behavior is intentional and acceptable for that path.
- Check
packageSubmissions and cause impact (reference 08) if the change involves an UpdateInfoTable event. The change may append entries to the package history from both contract and rate perspectives.
- Apply the submission history and freshness checklist if the change writes a new action, status transition, override, Q&A action, submit-like event, or contract/rate relationship change. Decide whether the action is CMS-visible, update
lastActionDate at the lowest-level DB write, update the relevant history builder/query, and add tests proving newest history aligns with stored freshness.
- Don't include
sharedRateRevisions in any new design — deprecated.
- Watch for
DRAFT_PARENT_PLACEHOLDER if the change involves draft rates without a submitted parent yet — the placeholder needs the outer parser to patch.
- Verify file paths and code shapes against the current state of the repo before depending on specifics. This reference was synthesized at a point in time; it can drift.
- Never hand-write Prisma migrations. If a schema change needs a migration, generate it with the Prisma CLI and review the generated SQL rather than manually creating
migration.sql files.
- Sandbox limitation for Prisma CLI. In the Codex sandbox, Prisma CLI commands that need database access may fail because the sandbox cannot connect to local Postgres (for example
localhost:5432). If a task requires prisma migrate, prisma db push, prisma migrate reset, or similar DB-connected Prisma commands, the human user should run them in their own shell and provide the results back for review.
- Migration transaction wrapper. When reviewing or adjusting generated Prisma migration SQL in this repo, make sure the migration statements are wrapped in an explicit
BEGIN; / COMMIT; transaction block.
- Never assume this skill is up-to-date. Scan the relevant code areas during work and check if the skill is out of date with the codebase.
- Prompt to update skill. If the skill is out of date with the codebase, prompt the human user on if they want to update the skill.