Architecture overview
Knowspread is a Rails application with two frontend generations and several API surfaces. This page is an orientation map for developers, managers, and technical owners. It does not replace code-level documentation, but it should make the main flows and source files easier to navigate.
Application surfaces
Section titled “Application surfaces”| Surface | Location | Audience | Notes |
|---|---|---|---|
| Rails backend | app/ | All clients | Owns persistence, authorization, APIs, jobs, billing, and domain logic. |
| Legacy AngularJS app | client/ | Existing product UI | Older frontend surface still present in the product. |
| React app | frontend/ | Newer product UI | Newer frontend surface. |
| Internal JSON API | app/controllers/api/v1/ | Product frontends | Company-space API, content editor/viewer, reporting, webhooks, settings. |
| Public REST API | app/controllers/rest/v1/ | External clients | Auth token, users, groups, content participations, contents, learning events. |
Request and domain flow
Section titled “Request and domain flow”flowchart LR Frontend[AngularJS or React UI] --> Api[api/v1 controllers] External[External system] --> Rest[rest/v1 controllers] Api --> Policy[Pundit policies] Rest --> Auth[REST auth] Policy --> Interaction[Interactions and domain services] Auth --> Interaction Interaction --> Models[ActiveRecord models] Models --> Jobs[Background jobs] Models --> Events[Wisper events] Jobs --> Models Events --> Listeners[Listeners] Models --> Serializers[Serializers] Serializers --> Frontend Serializers --> External
The preferred implementation style is thin controllers, authorization in
Pundit policies, command-like flows in app/interactions/, persistent domain
state in models, and response shaping through serializers.
Core domain flow
Section titled “Core domain flow”flowchart TD CompanySpace[CompanySpace] --> UICS[UserInCompanySpace] User[User] --> UICS CompanySpace --> UserGroup[UserGroup] UICS --> Membership[UserInUserGroup] UserGroup --> Membership CompanySpace --> CICS[ContentInCompanySpace] Content[Content] --> CICS Content --> Version[ContentVersion] CICS --> Assigned[AssignedContent] UserGroup --> Assigned UICS --> Assigned Assigned --> CA[ContentAssigner job] CA --> Source[ParticipationSource] Source --> Participation[ContentParticipation] Version --> Participation Participation --> Reporting[Reporting, certificates, exports]
The important product distinction is intent versus effective study state:
| Concept | Meaning |
|---|---|
AssignedContent | Intent: this content should be assigned to a user or group. |
ParticipationSource | Explanation: why a concrete study record exists. |
ContentParticipation | Effective study record for a user and content version. |
ContentLicense | Availability constraint for a user and content in a company space. |
Content assignment architecture
Section titled “Content assignment architecture”Content assignment is reconciled by the ContentAssigner family of classes:
| Component | Source | Responsibility |
|---|---|---|
ContentAssigner::Assigner | app/models/content_assigner/assigner.rb | Processes one assignment job and locks per user while reconciling state. |
ContentAssigner::AssignmentResolver | app/models/content_assigner/assignment_resolver.rb | Expands group and user assignment intent. |
ContentAssigner::SourceProducer | app/models/content_assigner/source_producer.rb | Produces desired participation sources. |
ContentAssigner::ParticipationResolver | app/models/content_assigner/participation_resolver.rb | Diffs desired sources against existing participations. |
ContentAssigner::LicenseResolver | app/models/content_assigner/license_resolver.rb | Ensures license availability is respected. |
ContentAssigner::DataSaver | app/models/content_assigner/data_saver.rb | Persists participation, source, and license changes. |
Operationally, this means assignment changes are not just simple CRUD writes. They trigger reconciliation that can add sources, update deadlines, reset some in-progress participations, preserve completed participations, and create license records.
Public REST API areas
Section titled “Public REST API areas”The current public REST routes include:
| Area | Route family | Purpose |
|---|---|---|
| Authentication | /rest/v1/auth/token | Obtain token for external API access. |
| Users | /rest/v1/users | List, read, create, update, archive, and unarchive users. |
| User groups | /rest/v1/user_groups | List, update, add users, remove users. |
| Company space | /rest/v1/company_space/info | Read company-space information. |
| Content participations | /rest/v1/content_participations | Read study state and certificate. |
| Contents | /rest/v1/contents | List, create, read, and update content. |
| Learning events | /rest/v1/learning_events | List, create, read, update events and days. |
Generated or source API documentation should still be treated as the endpoint contract. This page explains where those routes fit in the product.
Operational invariants
Section titled “Operational invariants”- A global
Usercan exist in multiple company spaces throughUserInCompanySpace. - A revoked user should remain traceable for study history, reports, and audit.
- A group should have one clear operational meaning because group membership can change assignment state.
- A content participation can have multiple participation sources.
- Completed participations should generally be preserved even when assignment sources change.
- Content version upgrades are not automatically equivalent to rewriting every in-progress study record.
- Public API integrations need a stable user identifier that survives e-mail or name changes.