milestone-delivery-trackingrun.md00_intake/stub.mdThe Progress section (milestones list, progress %, outcome summary) is dummy. The real milestone
model (packages/services/src/db/models/milestone.ts)
has only name, percentage, dueDate, order and invoice — it has no delivery status, no
owner, no customer-facing outcome label, and no blocks-go-live flag. So progress %, the outcomes list,
and the milestone cards cannot be real, and value-delivered (finance) and the health engine have no
completion signal to read.
milestone model with: delivery status (upcoming | in-progress | done), owner
(Expert | Your team | Customer), a customer-facing outcome label, and blocksGoLive.status (the delivery-side trigger from the
service-journey delivery flow).order.status / completion is exposed for customer-finance-summary and project-health-engine.validateMilestonePercentages) — reuse that for progress.isPaid/isApproved invoice virtuals with the new delivery status —
delivery-done is distinct from invoice-paid.touches: packages/services/src/db/models/milestone.ts (new fields),
packages/services/src/server (progress/outcome computation),
apps/web/components/dashboard/customer/progress-section.tsx.01_define/output/spec.mdThe customer dashboard's Progress section ("What is happening" — milestone cards, progress %, and the
outcomes summary) is rendered entirely from apps/web/lib/mock/customer.ts. The real milestone model
(packages/services/src/db/models/milestone.ts) carries only name, percentage, dueDate, order,
and invoice — it has no delivery status, no owner, no customer-facing outcome label, and no
blocks-go-live flag. So progress %, the outcomes list, and the milestone cards cannot be real, and the
downstream finance (value-delivered) and health-engine features have no completion signal to read. This
advances Build the Bridge / Q2 2026 Objective 3 — Validate Technical Infrastructure & Payout Flow:
real milestone delivery state is the completion signal the delivery → invoicing → health flow depends on.
milestone model with delivery-tracking fields:status: upcoming | in-progress | done (default upcoming).owner: Expert | Your team | Customer (default Expert).outcome: customer-facing outcome label (optional; the read envelope falls back to name when unset).blocksGoLive: boolean (default false).status is distinct from invoice paid/approved — the existing invoice isPaid/
isApproved virtuals are not reused or conflated; delivery-done and invoice-paid are independent.customer-project-foundation envelope — see dependency below):percentage across milestones whose status is done, as a whole number.
Reuses the existing invariant that a proposal's milestone percentages sum to 100
(validateMilestonePercentages), so a fully-delivered project reads 100%.order, exposing name, outcome, status, owner, blocksGoLive.ProgressSection (milestone cards, the progress bar, the outcomes list) to the
real envelope instead of mock/customer.ts. The component is already typed for this shape; this run
replaces its data source.status — a tenant-scoped service mutation
plus an authenticated route handler in apps/web/app/api. This is the delivery-side trigger from the
service-journey delivery flow; advancing a milestone to done moves the customer's progress % and
outcomes on the next read. No expert-dashboard UI is built this run (see Out of scope).status and the computed project progress) on the read
envelope so the downstream customer-finance-summary and project-health-engine features can consume
it without re-querying milestones.This is sequence 2 of the customer-dashboard-data epic and depends on customer-project-foundation
(sequence 1), which stands up the customer project read service + envelope and wires the dashboard page
off mock. Per the Define decision, this spec extends that foundation envelope rather than duplicating
it — so Build is gated on customer-project-foundation being merged first. This is a build-ordering
note, not an unresolved question about what to build.
milestone model carries status (upcoming|in-progress|done), owner
(Expert|Your team|Customer), outcome (string), and blocksGoLive (boolean), with sensible
defaults so existing milestone documents remain valid.order, each with real name,
outcome, status, owner, and blocksGoLive.done milestones (a
whole number, 100 when all are done) — not a hard-coded value.ProgressSection renders milestone cards, the progress bar, and the outcomes list from
the envelope with no import from lib/mock/customer.ts for this data.status via the API route; the mutation is
tenant-scoped, and the change is reflected in the customer envelope (progress + outcomes) on next read.status and the computed project progress are exposed on the envelope for
customer-finance-summary and project-health-engine to consume.move-to-completion-table) is a later run.customer-finance-summary consumes this completion signal).daysToGoLive (project-health-engine).status is added alongside, not merged into them.customer-project-foundation merges — a build-ordering note, recorded
under Dependency above, not an open requirement.)02_build/output/notes.mdfeat: milestone-delivery-tracking — delivery fields, envelope progress/outcomes, expert status APIpackages/services/src/db/models/milestone.ts: added delivery fields to IMilestone
and the schema — status (upcoming | in-progress | done, default upcoming),
owner (Expert | Your team | Customer, default Expert), outcome (optional,
customer-facing label), blocksGoLive (default false). Exported MilestoneStatus /
MilestoneOwner types and MILESTONE_STATUSES / MILESTONE_OWNERS enums. Delivery
status is added alongside the invoice paid/isApproved virtuals, not merged into them.packages/services/src/db/services/milestone/index.ts:MilestoneDeliveryRow type + findDeliveryByProposal(tenantId, proposalId) — ordered
delivery rows, coalescing defaults so milestones written before these fields still
render (upcoming / Expert / not blocking).advanceStatus(tenantId, expertId, milestoneId, status) — the expert-side delivery
trigger; tenant- and expert-scoped updateOne, returns whether a milestone matched.packages/services/src/db/services/customer-project/index.ts: extended the foundation
envelope. CustomerProjectEnvelope gains milestones: CustomerProjectMilestone[] and
outcomeSummary: string[]; project.progress is now the percentage-weighted sum of
done milestones (replacing the foundation's 0 placeholder). Milestones resolve via
the lead's accepted proposal (proposalService.findAcceptedByLead →
milestoneService.findDeliveryByProposal); outcome falls back to name, and
outcomeSummary is the outcomes of the not-yet-done milestones.packages/services/src/db/services/index.ts: export CustomerProjectMilestone.apps/web/app/api/milestones/[milestoneId]/status/route.ts: new PATCH route — the
API-only expert trigger. Clerk auth → role expert → tenant → app user, validates the
status, then milestoneService.advanceStatus. 401/403/400/404 as appropriate.apps/web/app/(app)/customer/dashboard/page.tsx: ProgressSection now reads
progress, milestones, and outcomeSummary from the envelope (no mock). daysToGoLive
stays on mock — it belongs to project-health-engine (out of scope).apps/web/lib/mock/customer.ts: removed activeProject, milestones, and
outcomeSummary — now envelope-sourced. (goLive.daysToGoLive stays for the
out-of-scope health layer.)The ProgressSection component needed no change — the foundation already typed it for this
shape; this run only swaps its data source.
status / owner / outcome / blocksGoLive with defaults; existing
documents stay valid (no migration — schema defaults + coalescing read).order with real name / outcome / status
/ owner / blocksGoLive.project.progress = percentage-weighted sum of done milestones (whole number, 100
when all done) — not hard-coded.outcomeSummary from the not-yet-done milestones' outcomes.ProgressSection renders cards, progress bar, and outcomes from the envelope — no
lib/mock/customer.ts for this data.status via PATCH /api/milestones/[milestoneId]/status; tenant- and expert-scoped; reflected in the
customer envelope on next read.status and computed project.progress are on the envelope for
customer-finance-summary and project-health-engine to consume.03_ship/output/changelog.mdPersona: Customer
Your customer dashboard now shows your real delivery progress instead of sample content:
This continues making your customer dashboard reflect your real project.
No end-user note for the Expert persona — the expert-side delivery trigger ships as a backend API only this run, with no dashboard surface yet.
03_ship/output/investor-update.mdWe've connected the customer dashboard's progress view to live delivery data — the second of nine steps turning the customer's home screen from a static demo into a real view of their engagement. This advances Build the Bridge and our Q2 objective to Validate Technical Infrastructure & Payout Flow: it proves real delivery status flows from the expert's work all the way through to what the customer sees.
03_ship/output/pr.mdtechnical/** documents structure, not individual db model fields or routes (it lists "Milestones" only as a demo-seed entity); the new fields + PATCH /api/milestones/[id]/status route change nothing documented there.apps/docs/app/business/service-journey/delivery/page.mdx — added the delivery status / owner / outcome / blocksGoLive fields to the Milestone Fields table, a "Delivery status" section covering the expert PATCH trigger and percentage-weighted progress, and clarified delivery status is distinct from the invoice isPaid / isApproved virtuals.03_ship/output/investor-update.md) + changelog entry published to apps/help/app/changelog/page.mdx in this PR (Customer persona; no end-user Expert note — the expert trigger is API-only this run)./code-review (high effort) over the diff — model, service, envelope, route, page, mock, docs — surfaced no correctness defects.advanceStatus scopes the updateOne to { tenantId, _id, expert: appUser._id }, so an expert can only advance milestones they own (milestone expert shares the user _id); progress is the percentage-weighted sum of done milestones; findDeliveryByProposal coalesces defaults so pre-existing milestones render; the lead → accepted proposal → milestones path degrades to empty progress/outcomes when there's no accepted proposal.[milestoneId] path param makes Mongoose throw a CastError → 500 rather than 404. This matches the existing api/brd/chat route (which casts leadId the same way); not worth diverging from the house pattern in this PR.status / owner / outcome / blocksGoLive with defaults; existing docs stay valid (schema defaults + coalescing read, no migration).order with real name / outcome / status / owner / blocksGoLive.project.progress = percentage-weighted sum of done milestones (whole number) — not hard-coded.outcomeSummary from the not-yet-done milestones' outcomes.ProgressSection renders from the envelope — no lib/mock/customer.ts for this data.status via PATCH /api/milestones/[milestoneId]/status; tenant- and expert-scoped; reflected in the customer envelope on next read.status and computed project.progress exposed on the envelope for customer-finance-summary and project-health-engine.gate:merge-approved).