Workstreams and contracts
WS1 through WS4 ownership and the shared functions between them
A workstream is a functional ownership boundary, not a separate deployable service. Every workstream uses the same Next.js process, Prisma client, and PostgreSQL database. The labels help contributors know where behavior belongs and which shared contract must remain stable.
Ownership table
| Workstream | User problem | Main pages/routes | Shared contract |
|---|---|---|---|
| WS1 | Industry needs a safe way to register and share capabilities. | /portal, /api/portal/** | Creates a submission, then calls routeSubmission. |
| WS2 | Government needs one ledger of organizations and interactions. | /gov, /api/gov/** | Writes organization, engagement, call, announcement, and admin records. |
| WS3 | Technical submissions need routing and POC alerts. | Matching helpers and notifications UI | routeSubmission, notifyUser, notifyGovByTags, notifyAllIndustry. |
| WS4 | Government needs discovery and portable exports. | Search and export pages/routes | searchAll backend seam and export role gate. |
WS1 to WS3 contract
routeSubmission(submissionId) receives only an ID. It reloads the submission, organization, tags, and portfolio nodes inside the service so callers cannot pass an untrusted score or arbitrary recipient. The service:
- Normalizes submission and organization tags.
- Calculates scores with the selected scorer: deterministic tag/keyword overlap by default, or Bedrock cosine similarity when
MATCHING_SCORER=bedrock. - Persists ranked
SubmissionMatchrows. - Changes
SUBMITTEDtoROUTEDwhen matches exist. - Notifies matching interest profiles without duplicate identical links.
- Writes
submission_routed.
This function is called after the submission row exists. Upload storage is complete before routing so the database never claims an object that failed its validation or write.
WS2 to WS3 notification contracts
notifyUser() creates one notification after checking a user's category setting. notifyUserForTags() performs the same check with event tags. notifyGovByTags() finds government users whose interest profile overlaps event tags. notifyAllIndustry() finds every industry user and applies their category setting. None sends external email; all create Notification rows for the in-app bell.
WS2 hierarchy contracts
getDescendantCommandIds() is a traversal utility for system-admin hierarchy operations. getScopedCommandIds() is the authorization policy used by admin lists. assertCommandInScope() is the per-record guard. Do not replace an exact scope check with a descendant traversal for command leads; the code intentionally denies downward inheritance to non-enterprise roles.
WS4 search contract
searchAll(query, options) returns { query, total, groups }, where each item contains a type, ID, title, snippet, URL, and optional metadata. setSearchBackend() allows an alternative backend (e.g. vector embeddings) without changing route callers. The default implementation is a hybrid: PostgreSQL full-text search against expression GIN indexes runs first — websearch_to_tsquery for plain queries and boolean/phrase syntax, or a translation to raw to_tsquery syntax when the query needs prefix wildcards, which websearch_to_tsquery cannot express —, degrading to typo-tolerant pg_trgm trigram fuzzy search (word_similarity()/%> over GIN trigram indexes, threshold tunable via SEARCH_SIMILARITY_THRESHOLD) when it finds nothing. Each stage is selectable on its own at deploy time via SEARCH_BACKEND (hybrid/fts/trigram/prisma — the original contains substring fallback).
Change checklist
When adding a feature:
Testing a cross-workstream change
A submission feature is a useful example because it crosses all four workstreams:
- WS1 validates the form and creates the submission.
- WS3 calculates matches and notifications.
- WS2 consumes the resulting organization/submission record and may log follow-up.
- WS4 discovers the record through search and includes eligible fields in an export.
Review the API response and database side effects at each boundary. A successful HTTP 201 proves only that the first route accepted the record; it does not prove a match or notification was produced.
Documentation contract
When a route changes, update three views:
| View | Minimum update |
|---|---|
| User feature page | New URL, walkthrough step, edge case. |
| API reference | Method, field constraints, status/error behavior, example. |
| Data/security page | New model relation or control if applicable. |
This prevents a route implementation from becoming the only place where a behavior is discoverable.