Skip to Content

← All archived runs

Run: remove-dead-misc-schema

run.md

Run: remove-dead-misc-schema

  • branch: claude/remove-dead-misc-schema-d7k9pp
  • pr: #812

02_define/output/spec.md

Spec: Drop the dead counter model and the misc dead schema weight

  • slug: remove-dead-misc-schema
  • personas: Admin
  • touches: packages/services/src/db/models, packages/services/src/db/services/activity, packages/services/src/db/services/demo-reset, packages/services/src/db/services/status, packages/services/src/db/migrations, apps/web/components/activity, apps/web/app/(app)/admin/demo-data
  • complexity: standard

Problem

The demo-tenant data audit (.icm/intake/demo-data-quality/_source/audit-report.md, findings #6, #7, #8, #9, #13, #15) proved a cluster of schema weight across five models that no code path writes and no surface can show. It is not inert: activity.oldStatus/newStatus are ObjectId refs to the retired status collection, populated on every feed read — a live MissingSchemaError risk — and rendered by two UI components that can therefore never display anything. Alongside them sit a wholly dead counter model, an invoice pdf field that stamps every document with a princexml sample URL, an isSubmitted virtual testing a status the engine does not have, a NOTIFICATION_TYPES enum whose UPPER_SNAKE values the real notification system never writes, and status-history enum arms that are unwritable by construction.

Dead weight of this kind is what makes the demo tenant hard to trust and hard to seed: every reader has to work out which fields are real. This run is step 5 of the demo-data-quality intake batch, which serves Refine the bridge / Q2-2026 Objective 1 — Establish Product-Market Fit with Vendor Partners by making the demo world an honest, leanly-modelled representation of the product. Removing the weight now also shrinks the surface the later seeding runs (stubs 9 and 10) have to reason about.

Proposed change

One cleanup run, no behaviour change any persona can observe. Grouped by model; each group is its own commit.

counter — delete the model. getNextId has zero callers (lead.requestId is allocated from tenant.leadRequestSequence); the only consumer is the demo reset purging an always-empty collection. Delete models/counter.ts and its models/index.ts re-export, drop "counter" from demo-reset/policy.ts PURGED_MODEL_FILES (and the paragraph documenting why it was listed), drop its PURGE_TARGETS entry and import in demo-reset/index.ts, and drop its admin-facing label from demo-reset-button.tsx. The drift test walks model files, so deleting the file removes the classification obligation. Reword the leadRequestSequence reset comment, which currently explains itself by reference to the counter collection. The migration drops the counters collection.

invoice — drop pdf and two dead virtuals. pdf has zero readers and defaults every document to https://www.princexml.com/samples/invoice/invoicesample.pdf. isSubmitted tests status === "invoice_submitted", which is not one of the engine's invoice statuses (invoice_draft/sent/disputed/paid/rejected); isPaid has zero readers. Remove the field, its IInvoice member, and both virtuals; $unset pdf in the migration.

activity — cut the retired-status refs and the dead audit columns. Remove oldStatus and newStatus from IActivity, IActivityPopulated and the schema; remove POPULATE_STATUSES and its four call sites in services/activity/index.ts; remove the status-change render blocks from activity-feed.tsx and activity-detail.tsx. In toCustomerActivityEntry the statusLabel derivation goes with them — the event label falls through to the humanised action name and then "Project update", and DELIVERY_STATUS_HINT tests the action name alone. Because newStatus is never written, statusLabel is already always undefined, so this is a code simplification with no output change. Also remove fieldChanged/beforeValue/afterValue (zero refs) and userId (never written, yet doubly indexed — an inline index: true plus a compound), and drop the inline index: true on leadId, which is a redundant prefix of the {leadId, createdAt: -1} compound the queries actually use. Status changes keep rendering from message/metadata, as they already do.

notification — delete the dead enum. NOTIFICATION_TYPES and NotificationType have zero consumers outside the model; the live system writes dot.case strings from notifications/*/types.ts. Delete both and type INotification["type"] as AnyNotificationType from notifications/preferences.js — a type-only import, erased at runtime, and the */types.ts modules it transitively names import nothing, so no cycle is introduced. No migration: the stored values are already the dot.case ones.

skill — drop metadata. Zero readers, zero writers. $unset in the migration.

status-history — narrow to what is actually written. entityType becomes the literal "lead" in both the TS type and the schema enum; bid and finance were never written and are unpopulatable, and "lead" is the only value any write path emits. entityId loses refPath: "entityType" and becomes a plain Schema.Types.ObjectId — nothing anywhere populates it, and the refPath is machinery inherited from the retired status-collection era. Narrow buildLeadProgressItems's workflow parameter from Workflow to "lead" (its one caller, leads/index.ts:968, already passes "lead") and drop the now-unnecessary as IStatusHistory["entityType"] cast. Remove the dead metadata field and the unused bare {status: 1} index. comment stays — it is write-only today but is a deliberate audit column, not proven dead.

One migration carries the database side: drop the counters collection, $unset the six removed data-bearing fields on their collections, and drop the four indexes (activities.userId_1, activities.userId_1_createdAt_-1, activities.leadId_1, statushistories.status_1). down is symmetric — recreate the indexes; the $unsets are not re-populated (the data was default-or-absent), which the migration documents inline.

Acceptance criteria

  • packages/services/src/db/models/counter.ts is deleted and no longer re-exported from models/index.ts; getCounterModel has zero references repo-wide.
  • demo-reset/policy.ts no longer lists "counter", demo-reset/index.ts has no counter purge target, and demo-reset-button.tsx has no counter label; policy.test.ts passes in CI unchanged (the drift guard is satisfied by the file's absence, not by an exclusion).
  • A demo reset still succeeds end to end and its admin-facing purge summary lists every remaining collection with no missing-label fallback.
  • invoice.pdf, isSubmitted and isPaid are gone from the model; no invoice surface changes (no reader existed).
  • The activity feed and the activity detail view render without a status-change block, and neither ActivityService read populates oldStatus/newStatus; grep -r "oldStatus\|newStatus" over packages/services/src and apps/web returns only the unrelated local variable in services/leads/index.ts.
  • activity.fieldChanged, beforeValue, afterValue and userId are gone from IActivity and the schema; leadId keeps only the {leadId, createdAt: -1} compound.
  • NOTIFICATION_TYPES and NotificationType are deleted; INotification["type"] is AnyNotificationType, and packages/services typechecks with no cyclic-import error.
  • skill.metadata is gone from ISkill and the schema.
  • status-history.entityType is the literal "lead" in type and schema enum; entityId has no refPath; metadata and the {status: 1} index are gone; buildLeadProgressItems takes "lead" and carries no entityType cast.
  • One migration file exists with symmetric up/down: drops the counters collection, $unsets invoice.pdf, skill.metadata, status-history.metadata and activity's oldStatus/newStatus/fieldChanged/beforeValue/afterValue/userId, and drops the four named indexes. Index drops use the existing dropIfExists idiom so a re-run is safe.
  • Existing statushistories documents carrying a legacy entityType are left in place — the narrowed enum validates writes only, and no write path rewrites them. Stated in the migration's header comment so the next reader does not mistake it for an oversight.
  • Format, lint, typecheck, test and the Vercel preview build are all green on the PR.

Out of scope

  • Invoice's paid/paidAt/isApproved dual-write of the status machine, and dueDate's missing write path — live behaviour, needs its own scope (audit finding #8).
  • Notification TTL/archiving — a real finding, but a product decision about retention, not cleanup (finding #7).
  • Quote/proposal duplicate-field consolidation (amounttotal, isDraftstatus) and quote.pdf — live write paths, same reason (finding #16).
  • status-history.comment — write-only today, but a deliberate audit column; not proven dead.
  • Dropping the workflows, status and nine other zombie collections, and the deleted-tenant production residue — destructive prod migrations deferred by decision D2 to the separate db-audit-findings triage. Note there is no workflow Mongoose model in the repo; the live status engine is the static packages/services/src/db/workflows/ module (workflows.json + index.ts), which this run does not touch.
  • The ~15 "unused index" suspects — the nightly $indexStats window is too weak to act on (D1). Only the four indexes named above are dropped, each because its field is being removed or it is a strict prefix of a compound that remains.
  • Milestone/expert-evidence transition guards (finding #5) — carved out at scope into their own stub.

Open questions

  • none

03_build/output/notes.md

Build notes: remove-dead-misc-schema

  • commits:
    • 87c18f7 refactor — delete the dead counter model
    • 06a5ccf refactor — drop invoice pdf and the dead virtuals
    • aca3474 refactor — cut activity's retired-status refs and dead columns
    • 22ac842 refactor — notification enum, skill and status-history
    • 88d0d39 feat — migration for the removed fields and indexes

What changed

  • db/models/counter.ts: deleted. getNextId had zero callers; lead.requestId comes from tenant.leadRequestSequence. Its models/index.ts re-export, demo-reset/policy.ts entry (and the paragraph explaining why it was listed), demo-reset/index.ts purge target and import, and the admin PURGED_LABELS entry in demo-reset-button.tsx all went with it. The leadRequestSequence reset comment no longer explains itself by reference to a collection that does not exist.
  • db/models/invoice.ts: pdf field, its IInvoice member, and the isSubmitted/isPaid virtuals removed.
  • db/models/activity.ts: oldStatus, newStatus, fieldChanged, beforeValue, afterValue and userId removed from IActivity, IActivityPopulated and the schema. The bare { leadId: 1 } inline index dropped as a redundant prefix of { leadId, createdAt: -1 }; a comment records why and points at the migration.
  • db/services/activity/index.ts: POPULATE_STATUSES and its four call sites removed. toCustomerActivityEntry no longer derives a statusLabel — the event label falls through to the humanised action name, and DELIVERY_STATUS_HINT tests the action name alone.
  • apps/web/components/activity/activity-feed/activity-feed.tsx, .../activity-detail/activity-detail.tsx, apps/web/lib/queries/workspace.ts: the three status-change render paths removed.
  • db/models/notification.ts: NOTIFICATION_TYPES and NotificationType deleted; INotification["type"] is now AnyNotificationType (type-only import from notifications/preferences.js).
  • db/models/skill.ts: metadata removed.
  • db/models/status-history.ts: entityType narrowed to the literal "lead" in both the TS type and the schema enum; refPath dropped from entityId; metadata and the bare { status: 1 } index removed, with a comment pointing at the migration.
  • db/services/status/build-progress-items.ts: buildLeadProgressItems takes workflow: "lead" and passes it straight through — the as IStatusHistory["entityType"] cast and the now-unused Workflow / IStatusHistory imports are gone.
  • db/migrations/1786665600000-remove-dead-misc-schema.ts: new.

One thing the spec's prose did not name

The spec's Proposed change listed two activity render paths (activity-feed.tsx, activity-detail.tsx); there is a thirdapps/web/lib/queries/workspace.ts:340-345, which resolved a persona-aware label from entry.newStatus?.name and fell back through newStatus?.displayName. It is the same dead field, so statusEvent was always null and the chain already landed on humanizeAction(entry.action?.name) || entry.message || "Update" — removing it changes no output. Handled here rather than deferred, because the spec's acceptance criterion is explicit that a repo-wide grep must come back clean; the criterion covers it even though the prose enumerated only two files.

Acceptance criteria status

  • counter.ts deleted, not re-exported, getCounterModel has zero references repo-wide — verified by grep.
  • policy.ts, demo-reset/index.ts and demo-reset-button.tsx carry no counter; policy.test.ts unchanged. Its third case ("classifies nothing that is not a tenant-scoped model") is what would have caught a stale name left behind, so the existing test proves the removal is complete rather than needing an edit.
  • Demo reset succeeds end to end with a complete purge summary — needs the preview; the code path is a Record keyed on PURGED_MODEL_FILES, so a missing label is a compile error, but the run itself is Verify's smoke.
  • invoice.pdf, isSubmitted, isPaid gone; no invoice surface changed (no reader existed).
  • No ActivityService read populates the statuses; no surface renders a status-change block. grep -rn "oldStatus\|newStatus" over packages/services/src and apps/web now returns only the newStatusName local in services/leads/index.ts, as the criterion specifies.
  • fieldChanged, beforeValue, afterValue, userId gone; leadId keeps only the compound.
  • NOTIFICATION_TYPES/NotificationType deleted; INotification["type"] is AnyNotificationType. No cycle: preferences.ts imports only the per-domain types.ts modules, which import nothing, and the import is type-only so it is erased at runtime.
  • skill.metadata gone.
  • status-history.entityType is "lead" in type and enum; no refPath; metadata and {status: 1} gone; buildLeadProgressItems takes "lead" and carries no cast.
  • One migration, symmetric where it can be: drops counters, $unsets the eight fields, drops the four indexes via a dropIndexIfExists helper; down recreates all four indexes.
  • Legacy entityType documents left in place, stated in the migration's header.
  • Format / lint / typecheck / test / preview build green — CI's to answer; read off the Quality check run and the Vercel preview.

Notes for Verify

  • The down is a deliberate partial inverse. It recreates the four indexes but does not repopulate the unset fields or restore the counters documents, and it does not throw. The reasoning is spelled out in the migration header: the removed values were the schema default or absent, and throwing would make the index rollback unreachable. This is the same shape as 1784400000000-remove-service-fee.ts — loud header, usable down. Worth a look if you disagree with the trade.
  • unsetFields is not tenant-scoped, on purpose. These fields are retired for every tenant at once. Flagged because AGENTS.md requires bulk updates to be tenant-safe when they should be per-tenant — this one should not be, and says so inline.
  • Migration timestamp 1786665600000 sits one day after the current head migration (1786579200000-reflag-demo-tenant-preview). Nothing else in the demo-data-quality batch has merged yet, so no ordering conflict today — but stubs 1, 2 and 4 also carry migrations and are meant to run in parallel. If one of them lands first with a higher timestamp, this stays correctly ordered regardless; ts-migrate-mongoose tracks by name, not by position.
  • Two criteria are the factory's or Verify's, marked [ ] above rather than dropped: the demo-reset smoke on the preview, and the CI check rollup.
  • The activity-detail grid keeps grid-cols-2 sm:grid-cols-3 with a single remaining cell. Left as-is — restyling the panel is outside this run's scope.

Context budget: within the Inputs table.

04_verify/output/verify.md

Verify: remove-dead-misc-schema

  • production-readiness: run — 2 findings fixed on branch (88af6f2), 5 fed to intake. No blockers.
  • code-review: medium (spec complexity: standard) — 1 finding, not fixed (judgement call, below). The CI Review diff against CONVENTIONS.md job reported skipped, so the review was run here.
  • security-review: not required — the diff touches no auth, payments, PII or route policies. ROUTE_POLICIES is untouched; no new routes, server actions, cron routes or external callbacks; the only auth-adjacent file is demo-reset-button.tsx, and only to delete one label string.
  • playwright: TODO — manual DoD smoke performed instead

A correctness regression this stage caught and fixed

Production-readiness found that the original migration's drop of statushistories.status_1 was justified by the wrong test. The Build-stage reasoning was "no query in code seeks on status alone" — but a single-field index serves any query carrying an equality predicate on that field, whatever else it filters on. vendor/index.ts::loadDeliveredSpans matches {tenantId, entityType, status, changedAt} with no entityId, which makes it the one statushistories read the surviving {tenantId, entityId, entityType, changedAt} compound cannot serve past its tenantId prefix. Dropping status_1 outright would have left that aggregate filtering the whole tenant's status history in memory — on a single-dominant-tenant database, effectively a full collection scan.

Fixed in 88af6f2: the migration now creates a tenant-leading {tenantId, status, changedAt: -1} before dropping the bare index, and down reverses both in the correct order. The replacement is ESR-correct for that match and a better fit than the index it retires — the old one did not lead with tenantId, against house convention.

This is a deviation from the letter of the spec, which said the migration "drops the four named indexes". It now drops four and creates one. The deviation serves the spec's intent (remove dead weight without regressing live queries) rather than contradicting it, but it is Jamie's to accept.

DoD smoke (on the preview — each line says who verified it)

Preview: https://web-git-claude-remove-dead-misc-schema-d7k9pp-sustentus.vercel.app — responds HTTP 200 unauthenticated (agent).

  • counter.ts deleted, not re-exported, getCounterModel zero references — repo-wide grep clean across apps/* and packages/*/src; confirmed independently by both reviews (agent)
  • policy.ts / demo-reset/index.ts / demo-reset-button.tsx carry no counter; policy.test.ts unchanged and green — QualityRun tests passed (agent + CI)
  • Demo reset succeeds end to end, purge summary completeoperator, needs an admin sign-in on the preview. Static evidence only: PURGE_TARGETS is a Record keyed on PURGED_MODEL_FILES, so a missing entry is a compile error, and typecheck is green. That is not the same as watching a reset run.
  • invoice.pdf, isSubmitted, isPaid gone; no invoice surface changed (no reader existed before or after) (agent)
  • No ActivityService read populates oldStatus/newStatus; the three render paths are gone. grep -rn "oldStatus\|newStatus" over packages/services/src + apps/web returns only the newStatusName local in services/leads/index.ts, exactly as the criterion specifies (agent)
  • Activity feed + detail + workspace render correctly signed inoperator. The code paths are traced and the removed branches were provably unreachable, but "it still looks right" on the three surfaces needs a signed-in look.
  • activity.fieldChanged/beforeValue/afterValue/userId gone; leadId keeps only the {leadId, createdAt: -1} compound (agent)
  • NOTIFICATION_TYPES/NotificationType deleted; INotification["type"] is AnyNotificationType; no cyclic-import error — typecheck green (agent + CI)
  • skill.metadata gone (agent)
  • status-history.entityType is "lead" in type and enum; no refPath; metadata gone; buildLeadProgressItems takes "lead" with no cast (agent)
  • Migration present, symmetric, index drops guarded by dropIndexIfExistsand it actually ran: the Migrate preview database check passed on this branch, which is stronger evidence than a code read (agent + CI). Note the create-before-drop amendment above.
  • Legacy entityType documents left in place, stated in the migration header. Both reviews independently confirmed no read path save()s one back through the narrowed enum — every read is .lean() or an aggregate (agent)
  • Format / lint / typecheck / test green — Quality Project success. Re-running against 88af6f2; the result must be green before Ship (agent + CI)
  • auth: not touched — no sign-in flow, route policy or Clerk surface in the diff (agent)
  • payments: not touched — invoice pdf/virtuals are display-layer dead weight, not the payment state machine, which this run explicitly leaves alone (agent)
  • notifications: none expected to change. The diff narrows a TypeScript type; it adds no notifyX call and changes no send path. create.ts now types type as the union the senders already pass, so every existing notification keeps firing identically (agent)

Walkthrough clip: not posted. The contract asks for one on owner-facing changes; this run has no observable behaviour change for any persona by design, so there is nothing to show. Say the word if you want one of the activity surfaces recorded anyway.

Findings & cleanup

Fixed on branch (88af6f2):

  1. statushistories.status_1 drop regressed loadDeliveredSpans — replaced with a tenant-leading index, as above. The one genuine bug this stage found.
  2. INotification["type"] narrowing was unenforced at the write boundary — CreateNotificationInput["type"] was a bare string, so the model's "senders cannot drift apart" comment was not actually true. Now typed AnyNotificationType. (send-email.ts already cast to this union in four places, which is good evidence the union was always the intent.)
  3. Cosmetic: stray * comment line left in policy.ts by the counter removal.

Not fixed — judgement call for you:

  1. apps/web/components/notifications/notification-inbox-list.tsx:38-53 — the typeAccent map is keyed on PROPOSAL_RECEIVED, INVOICE_PAID, SLA_BREACH …, i.e. exactly the UPPER_SNAKE constants this PR deletes. Senders have written dot.case for some time, so typeAccent[n.type] has always fallen through to DEFAULT_ACCENT and every inbox row renders the same blue border. Both reviews found this independently.

    I did not fix it here. The map is dead in the same way the enum was, but repairing it changes visible UI — rows would start rendering distinct accent colours — and this run's spec promises no behaviour change any persona can observe. Picking those colours is a design decision, not a cleanup. It belongs in intake, which is also where production-readiness put it. Tell me if you'd rather it rode along here.

Fed to intake (out of this run's scope, recorded so they are not lost):

  1. Customers see internal action jargon in the workspace activity feed — action names are seeded UPPER_SNAKE, so humanizeAction yields "Brd flagged", "Qualified out". Unchanged by this PR (the newStatus branch was always null), but the comment removed at that site was the only in-repo record that the label is meant to be persona-safe via statusService.resolveLabel.
  2. unsetFields on activities is a collection scan — six $exists predicates with no supporting index, run on merge. Fine at current volume; worth knowing before the collection grows.
  3. apps/docs/.../lead-intake/page.mdx:51 still says requestId "is generated via an atomic counter" — the collection this PR drops. Pre-existing drift (allocation moved to tenant.leadRequestSequence earlier), but this run makes it plainly wrong. docs-sync at Ship should reconcile it — flagging here so Ship does not miss it.
  4. Notification TTL/archiving and the invoice paid/paidAt/isApproved dual-write remain out of scope, as the spec states.

Process note: the first /code-review pass ran against a stale local main ref (67e3353, behind origin/main at 87183b0) and reviewed 345 files of already-merged history, returning three findings that were not from this PR. Caught by the diff size, re-run against origin/main...HEAD (21 files). Local main has been fast-forwarded so it does not recur.

Context budget: within the Inputs table.

05_ship/output/investor-update.md

The data layer now describes only what the product does

Who it's for: Admin, and every persona indirectly What shipped: A dead database model removed, plus unused fields, indexes and enums across five more, with a migration that clears them from the live database. Why it matters: Refine the Bridge — fewer places a real bug can hide.

One removal mattered: activity records referenced a retired collection on every read.

Dig deeper: https://github.com/sustentus/sustentus/pull/812

05_ship/output/release.md

Ship: remove-dead-misc-schema

  • pr: #812 · merge: authorised — Ready to merge ticked by Jamie; this commit rides the squash
  • CI: green on f171c45Quality Project (format · lint · typecheck · test), Migrate preview database, Audit database, and the four advisory pipeline checks all passed. Migrate production database and Review diff against CONVENTIONS.md skipped by design.
  • technical docs: apps/docs/app/technical/demo-environment/page.mdx — "via the tenant counter" → "via tenant.leadRequestSequence", matching what the same page already said correctly further down.
  • business docs: apps/docs/app/business/service-journey/lead-intake/page.mdx — the requestId section said the number "is generated via an atomic counter", which named the collection this run deletes. Now: allocated atomically from a sequence held on the tenant. The allocation is still genuinely atomic (an aggregation-pipeline $inc in allocate-next-lead-request-id.ts), so only the word "counter" was wrong.
  • release notes: ship-note-only — no end-user note. Nothing here is observable to any persona: every field removed was unwritten, unread, or a schema default, and the spec's own framing is "no behaviour change any persona can observe". A changelog entry would announce nothing.
  • sent: queued — ship-note.yaml fires on this merge and emails the note to #product-update. Not sent at the time of writing; the merge is the trigger.

Acceptance check (vs spec)

  • counter.ts deleted, not re-exported, getCounterModel zero references — repo-wide grep, confirmed independently by the production-readiness and code-review passes
  • policy.ts / demo-reset/index.ts / demo-reset-button.tsx carry no counter; policy.test.ts unchanged and green in CI
  • Demo reset succeeds end to end with a complete purge summary — operator-verified; Jamie ticked Ready to merge, which is the sign-off on the two preview checks the agent could not perform. Not demonstrated in-session.
  • invoice.pdf, isSubmitted, isPaid gone; no invoice surface changed
  • No ActivityService read populates oldStatus/newStatus; all three render paths removed (feed, detail, and the workspace.ts path the spec's prose did not enumerate)
  • activity.fieldChanged/beforeValue/afterValue/userId gone; leadId keeps only the {leadId, createdAt: -1} compound
  • NOTIFICATION_TYPES/NotificationType deleted; INotification["type"] is AnyNotificationType; no cyclic import — typecheck green
  • skill.metadata gone
  • status-history.entityType is "lead"; no refPath; metadata and the bare {status: 1} index gone; buildLeadProgressItems takes "lead" with no cast
  • One migration, symmetric where it can be — and it ran: Migrate preview database passed against a real database, twice (before and after the Verify amendment)
  • Legacy entityType documents left in place, stated in the migration header
  • Format / lint / typecheck / test / preview build green

Deviation from the spec, carried forward

The spec said the migration "drops the four named indexes". It drops four and creates one. Production-readiness found that dropping statushistories.status_1 would regress vendor/index.ts::loadDeliveredSpans — the one statushistories read with no entityId, which a bare {status: 1} was serving. The migration now creates a tenant-leading {tenantId, status, changedAt: -1} before dropping the old index. Recorded in verify.md, and accepted by the same tick that authorised the merge.

Left for intake, deliberately

  • notification-inbox-list.tsx keys its accent map on the UPPER_SNAKE constants this run deletes, so every inbox row renders the default accent. Not fixed here: repairing it changes visible UI in a run that promises no observable change, and the colour choices are a design decision.
  • Customer-facing action jargon in the workspace activity feed (Brd flagged, Qualified out).
  • unsetFields on activities is a collection scan — fine at current volume.
  • Notification TTL/archiving and the invoice paid/paidAt/isApproved dual-write, both out of scope per the spec.

Context budget: within the Inputs table.