-
Notifications
You must be signed in to change notification settings - Fork 0
Core Functions
This page documents every major subsystem and feature in SPARC, the Systemized Policy and Regulatory Controls platform for NIST 800-53 compliance management.
- Document Processing Pipeline
- OSCAL Export & Validation
- SSP Wizard
- SSP Enrichment
- Control Mapping
- Evidence & Attestation Management
- Project Management
- Audit Logging
- Heatmap Analytics
- Document Duplication & Copy
- SAP Generation
- JSON Export
- REST API
- Dark Mode & Theming
All document uploads follow a unified flow driven by the FileUploadable controller concern and the DocumentTypeRegistry model. This architecture ensures consistent behavior across all six document types.
| Type Key | Document Class | Control Class | Field Class | Allowed Extensions |
|---|---|---|---|---|
ssp |
SspDocument |
SspControl |
SspControlField |
.xlsx, .xls, .json, .xml
|
sar |
SarDocument |
SarControl |
SarControlField |
.xlsx, .xls, .json, .xml
|
cdef |
CdefDocument |
CdefControl |
CdefControlField |
.xml, .json
|
profile |
ProfileDocument |
ProfileControl |
ProfileControlField |
.json, .xml
|
sap |
SapDocument |
SapControl |
SapControlField |
.json |
poam |
PoamDocument |
PoamItem |
(none) |
.json, .xml
|
The DocumentTypeRegistry (located at app/models/document_type_registry.rb) is a centralized registry that maps each type key to its associated classes, file extensions, parser services, and user-facing messages. Each entry is a Data.define struct:
Entry = Data.define(
:document_class,
:control_class,
:field_class,
:document_fk,
:allowed_extensions,
:parser_map,
:file_prefix,
:success_message
)Usage:
entry = DocumentTypeRegistry.for(:sar)
entry.document_class # => SarDocument
entry.parser_map # => { "excel" => SarExcelParserService, "json" => SarJsonParserService, ... }The FileUploadable concern (app/controllers/concerns/file_uploadable.rb) implements a five-step pipeline that every document controller shares:
- Validate file presence -- returns an error flash if no file is attached.
-
Detect file type -- maps the file extension against
registry.allowed_extensionsto determine the parser format (e.g.,.xlsxmaps to"excel"). -
Write to persistent path -- the file is written to
tmp/with a safe prefix and random hex suffix. File path components come exclusively from frozen constants (SAFE_PREFIXES,SAFE_EXTENSIONS), satisfying Brakeman taint analysis. -
Create document record -- a new document is created with
status: "pending", the uploaded file is attached via Active Storage, andDocumentConversionJobis enqueued. - Error handling -- on failure, the temporary file is cleaned up and the user sees the error.
# In any document controller:
def create
handle_file_upload(:ssp, param_key: :ssp_document)
endLarge files are processed asynchronously via Sidekiq:
-
DocumentConversionJobreceives the type key, document ID, and file path. - The job instantiates the appropriate parser service from the registry.
- Document status transitions:
pending->processing->completed(orfailed). - The
ConversionJobmodel tracks job state for monitoring.
Each document type exposes a status endpoint that returns JSON, enabling the frontend to poll for completion:
GET /:document_type/:id/status
# => { "status": "completed" }
Each parser service handles a specific input format and populates the three-level model hierarchy (Document -> Controls -> Fields).
File: app/services/ssp_excel_parser_service.rb
Parses Excel spreadsheets into SSP documents. Uses a three-phase batch insert strategy:
-
Phase 1: Batch insert parent controls (rows that have a
control_id). -
Phase 2: Batch insert child controls (rows without
control_id, representing provider statements) withparent_idresolved from Phase 1 results. -
Phase 3: Batch insert all
SspControlFieldrecords linked to their respective controls.
Column mapping is loaded from the data mapping schema (lib/data_mappings/ssp_excel.json) via DataMappingSchema.load(:ssp_excel).
File: app/services/sar_excel_parser_service.rb
Parses multi-sheet Excel workbooks where each sheet represents a section. Key behaviors:
- Iterates all sheets, treating each sheet name as the section identifier.
- Tracks subject asset/environment pairs by splitting the subject column value on
"|". - Denormalizes
control_family(extracted fromcontrol_idprefix) andcached_resultdirectly onto the control record for query performance. - Preserves sheet metadata (sheet order and original headers) in
excel_metadataon the document record for round-trip Excel export. - Uses the shared
BatchInsertableconcern for bulk insertion.
Files: app/services/cdef_json_parser_service.rb, app/services/cdef_xccdf_parser_service.rb
-
CdefJsonParserService-- parses OSCAL Component Definition JSON. -
CdefXccdfParserService-- parses DISA STIG XCCDF XML format, extracting rules, check content, and fix text.
Files: app/services/profile_json_parser_service.rb, app/services/profile_xml_parser_service.rb
Parse OSCAL Profile baselines from JSON and XML formats.
File: app/services/catalog_import_service.rb
Imports NIST 800-53 control catalogs from two formats:
-
OSCAL JSON -- standard NIST OSCAL catalog schema from
oscal-content. - NIST XML -- SP 800-53 SCAP feed schema v2.0.
Key behaviors:
-
Zero-padding: Single-digit control numbers are padded (
AC-1becomesAC-01,AC-10stays unchanged). -
Sub-parts as siblings: Statement parts are stored as sibling
CatalogControlrecords with hierarchical IDs:AC-01,AC-01a,AC-01a.1,AC-01a.1.(a). - Format auto-detection: Inspects file extension and content structure to determine OSCAL JSON vs. NIST XML.
-
Upsert semantics: Uses
find_or_initialize_byfor catalogs, families, and controls, so re-importing updates existing records rather than creating duplicates. - Extracts guidance data including statements, supplemental guidance, related controls, and references.
- Supports all 20 NIST 800-53 control families via a
FAMILY_NAME_TO_CODElookup table.
File: app/services/sap_json_parser_service.rb
Parses OSCAL Assessment Plan documents from JSON.
Files: app/services/poam_json_parser_service.rb, app/services/poam_xml_parser_service.rb
Parse Plan of Action & Milestones documents from JSON and XML. The POA&M model uses PoamItem rather than a generic control class, reflecting the distinct structure of POA&M findings.
File: app/services/concerns/batch_insertable.rb
Provides high-throughput bulk insertion shared across parser services. Uses the activerecord-import gem to batch insert records within a single database transaction:
- Control batch size: 5,000 records per batch
- Field batch size: 10,000 records per batch
batch_insert_records(
control_class: SarControl,
field_class: SarControlField,
document_fk: :sar_document_id,
control_attrs: control_attrs, # Array of attribute hashes
field_entries: field_entries # Array of [control_index, field_name, field_value]
)Control IDs are returned via returning: :id so field records can be linked to their parent controls by index.
Directory: lib/data_mappings/
Files: ssp_excel.json, sar_excel.json
Service: app/services/data_mapping_schema.rb
Data mapping files are vendor-neutral, declarative JSON definitions that describe how source columns (Excel headers) map to internal model attributes and fields. Each mapping entry includes:
-
source_header-- the normalized column header from the source file. -
key-- the internal attribute or field name. -
storage-- one ofcontrol_attribute(stored on the model),control_field(stored in the field table), orsubject(split on"|"into asset/environment). -
editable-- whether the field is editable in the UI. -
validation-- optional rules (e.g.,allowed_values). -
oscal_mapping-- how the field maps to OSCAL export targets.
schema = DataMappingSchema.load(:ssp_excel)
schema.column_map # => { "paragraph/reqid" => { key: :control_id, control_attr: true } }
schema.editable_fields # => ["status", "private_implementation", ...]
schema.oscal_mappings # => { "status" => { "target" => "prop", ... } }SPARC provides full OSCAL v1.1.2 JSON export for all document types, with schema validation against the official NIST JSON schemas.
Each document type has a dedicated OSCAL export service:
| Service | Document Type | OSCAL Root Key |
|---|---|---|
OscalSspExportService |
SSP | system-security-plan |
OscalComponentDefinitionExportService |
Component Definition | component-definition |
OscalSarExportService |
Assessment Results | assessment-results |
OscalProfileExportService |
Profile | profile |
OscalCatalogExportService |
Catalog | catalog |
OscalPoamExportService |
POA&M | plan-of-action-and-milestones |
OscalAssessmentPlanExportService |
Assessment Plan | assessment-plan |
OscalMappingExportService |
Control Mapping | mapping-collection |
File: app/services/oscal_ssp_export_service.rb
Builds a complete OSCAL v1.1.2 System Security Plan JSON document. The top-level structure includes:
- metadata -- title, version, oscal-version, last-modified, roles, parties, revisions.
- import-profile -- reference to the baseline profile.
- system-characteristics -- system IDs, name, description, security sensitivity level, security impact levels, system status, authorization boundary, network architecture, data flow.
-
system-implementation -- users, components (including
this-system), leveraged authorizations, inventory items. - control-implementation -- implemented requirements with by-components, statements (private/public/inherited), and props (status, control type, origination, responsible entities).
- back-matter -- preserved resources from import metadata.
The service works uniformly: enriched relational data is used when available (regardless of whether the SSP was created via wizard or Excel import), falling back to placeholder values only when no data exists.
service = OscalSspExportService.new(ssp_document)
json_string = service.export # validates, raises on failure
json_string = service.export_unvalidated # skips validation
result = service.validation_result # inspect errors without raisingSupports NIST, CIS, and DISA source mappings for component definitions.
Exports assessment results with synthesized observations and findings.
File: app/services/oscal_schema_validation_service.rb
Validates OSCAL JSON against the official NIST v1.1.2 schemas using the json_schemer gem (Draft 2020-12 support). Supports all eight OSCAL model types:
SCHEMA_MAP = {
component_definition: { file: "oscal_component_schema.json", root_key: "component-definition" },
ssp: { file: "oscal_ssp_schema.json", root_key: "system-security-plan" },
assessment_plan: { file: "oscal_assessment-plan_schema.json", root_key: "assessment-plan" },
assessment_results: { file: "oscal_assessment-results_schema.json", root_key: "assessment-results" },
poam: { file: "oscal_poam_schema.json", root_key: "plan-of-action-and-milestones" },
profile: { file: "oscal_profile_schema.json", root_key: "profile" },
catalog: { file: "oscal_catalog_schema.json", root_key: "catalog" },
mapping: { file: "oscal_mapping_schema.json", root_key: "mapping-collection" }
}The validation service performs:
- Structural pre-check -- verifies the expected root key is present.
-
Full schema validation -- runs
json_schemeragainst the NIST schema. - Error formatting -- produces human-readable messages (caps at 50 errors) with data pointer paths, missing required properties, enum violations, type mismatches, and pattern failures.
Schema files are cached after first load. An internal preprocess_schema step rewrites NIST anchor-style $ref values to standard JSON Pointer format (#/definitions/X) so json_schemer can resolve them locally without network access.
Three download modes are available in the UI for every document type:
-
Validated (
download_oscal_validated) -- validates against the NIST schema; fails with an error if invalid. -
Unvalidated (
download_oscal_unvalidated) -- always downloads, skipping validation. -
Auto (
download_oscal) -- attempts validated export first; on failure, redirects to the unvalidated endpoint.
File: app/services/oscal_metadata_inheritance_service.rb
Preserves and merges OSCAL metadata (roles, parties, revisions, oscal-version) along the artifact chain:
ControlCatalog -> ProfileDocument -> SspDocument -> SapDocument -> SarDocument
SspDocument -> PoamDocument
Uses OSCAL resolution rules: child overrides parent for scalar fields; array fields (roles, parties) are merged with child entries taking precedence.
OscalMetadataInheritanceService.new(ssp_document).resolve!- PR #53 -- OSCAL schema validation and SSP export (#45)
- PR #67 -- Full schema uplift for CDEF, Catalogs, Profiles, SAR (#58)
- PR #64 -- OSCAL metadata management & inheritance (#52)
- PR #60 -- SSP creation wizard, OSCAL import, enrichment
File: app/services/ssp_wizard_service.rb
The SSP Wizard allows users to create a System Security Plan from scratch rather than importing from Excel. The entire process runs in a single database transaction.
-
Profile (baseline) -- a
ProfileDocumentthat serves as the control source. -
Component Definitions (CDEFs) -- optionally attach one or more
CdefDocumentrecords. -
System metadata:
- System name and description
- System status (default:
"operational") - Security sensitivity level
- Security objectives (Confidentiality, Integrity, Availability)
- Authorization boundary description
-
Create SspDocument with
creation_method: "wizard"andstatus: "processing". - Create "this-system" component -- the OSCAL-required default component representing the system itself.
- Create default information type -- placeholder for system information.
-
Create default user -- "General User" with the
system-ownerrole. -
Import CDEF components (if selected) -- creates
SspDocumentCdefDocumentjoin records and correspondingSspComponententries. -
Populate controls from profile -- iterates through the profile's controls, creating
SspControlrecords with default fields (status defaults to "Deferred") and athis-systemby-component entry. -
Auto-fill from CDEFs (if selected) -- matches CDEF controls to SSP controls by NIST ID, populates
private_implementationfrom the CDEF'simplementation_narrative, upgrades status from "Deferred" to "Implemented" when narrative is present, and creates by-component entries. -
Mark complete -- sets
status: "completed".
Control IDs are normalized for matching: lowercased, whitespace replaced with hyphens, parentheses converted to dots, and multiple dots collapsed.
- PR #60, Issue #30
SSP Enrichment allows users to uplift legacy Excel-imported SSPs with full OSCAL metadata that cannot be expressed in the spreadsheet format.
The SSP enrichment UI (enrich / update_enrich actions in SspDocumentsController) supports adding and editing:
- System-level fields: system name, description, system ID, system status, date authorized, security sensitivity level, security objectives (C/I/A), authorization boundary description, network architecture description, data flow description.
- Components: type, title, description, status state, status remarks, purpose, responsible roles, protocols, props, links, remarks.
- Users: UUID, title, description, short name, role IDs, authorized privileges, props, links, remarks.
- Information types: title, description, categorizations, confidentiality/integrity/availability impact (base, selected, adjustment justification).
- Leveraged authorizations: title, party UUID, date authorized.
The controller includes sync helpers that handle create/update/delete of nested OSCAL entities. Each entity section (components, users, information types) can be managed independently, with changes applied via the update_enrich action.
The OscalSspExportService works uniformly -- it uses enriched relational data when available regardless of whether the SSP was created via wizard or Excel import. Excel-imported SSPs that have been enriched via the UI get proper exports with the enriched data, while un-enriched SSPs continue to export valid OSCAL with sensible defaults.
Models: app/models/control_mapping.rb, app/models/control_mapping_entry.rb
Control Mapping provides cross-walk functionality between two control catalogs, such as NIST SP 800-53 Rev 4 to Rev 5, or NIST to ISO 27001.
ControlMapping represents the overall mapping collection:
belongs_to :source_catalog, class_name: "ControlCatalog"
belongs_to :target_catalog, class_name: "ControlCatalog"
has_many :control_mapping_entries, dependent: :destroyValidated attributes:
-
name-- required. -
uuid-- auto-generated, unique. -
status-- one of:draft,complete,not-complete,deprecated,superseded. -
method_type-- one of:human,automation,hybrid. -
matching_rationale-- one of:syntactic,semantic,functional.
ControlMappingEntry represents an individual source-to-target control relationship:
-
source_control_idandtarget_control_id-- required. -
source_typeandtarget_type-- either"control"or"statement". -
relationship-- aligned with NIST IR 8477 set-theory relationships:equal,equivalent,subset,superset,intersects. - Unique constraint on
(control_mapping_id, source_control_id, target_control_id).
Mappings follow a lifecycle with status transitions:
- Draft -- initial state for authoring.
- Complete (publish) -- mapping is finalized and ready for use.
- Deprecated / Superseded -- end-of-life states.
The published scope returns mappings with status: "complete".
Mappings can be exported to OSCAL mapping-collection JSON via OscalMappingExportService.
- PR #118 (Issue #98), Issue #119
Models: app/models/evidence.rb, app/models/attestation.rb, app/models/evidence_control_link.rb
Evidence records support uploading compliance artifacts with Active Storage. Each evidence record tracks:
-
Evidence types (enum):
artifact,screenshot,log,config_export,scan_result,signed_statement,policy_document,test_result. -
Status lifecycle (enum):
draft->collected->reviewed->attested->expired. -
File integrity: SHA256 hash computed via
compute_file_hash!for verification:
def compute_file_hash!
return unless file.attached?
self.file_hash = Digest::SHA256.hexdigest(file.download)
self.file_content_type = file.content_type
self.original_filename = file.filename.to_s
self.file_size = file.byte_size
save!
endThe EvidenceControlLink model links evidence to specific controls across any document type:
DOCUMENT_TYPES = %w[SspDocument SarDocument SapDocument CdefDocument PoamDocument]Each link stores control_id, document_type, and document_id, with a uniqueness constraint on the combination. This enables a single piece of evidence to be associated with controls across multiple documents.
The Attestation model provides formal verification by authorized personnel:
-
attester_name-- required. -
statement-- the attestation text (required). -
attested_at-- timestamp (required). -
role-- one of:control_owner,system_owner,isso,ciso,assessor,authorizing_official. -
attester_email-- email address of the attester. -
signature_hash-- SHA256 hash generated from a payload of attester details, statement, timestamp, and evidence ID:
def generate_signature!
payload = "#{attester_name}|#{attester_email}|#{statement}|#{attested_at.iso8601}|#{evidence_id}"
self.signature_hash = Digest::SHA256.hexdigest(payload)
save!
end- PR #75 (Issue #31)
Models: app/models/project.rb, app/models/boundary.rb, app/models/project_membership.rb
Projects serve as the top-level container for compliance artifacts, organizing them around a system boundary.
A project aggregates:
-
ssp_document(has_one) -
sap_document(has_one) -
sar_document(has_one) -
poam_documents(has_many) -
evidences(has_many) -
boundaries(has_many) -
project_memberships(has_many)
Status (enum): draft, active, authorized, deauthorized.
The artifact_summary method provides a quick overview:
def artifact_summary
{
ssp: ssp_document&.name,
sap: sap_document&.name,
sar: sar_document&.name,
poam_count: poam_documents.count,
boundary_count: boundaries.count,
component_count: boundaries.joins(:cdef_documents).count
}
endSystem boundaries define network and security perimeters. Each boundary:
- Belongs to a project.
- Has an
environmentenum:production,development,staging,test. - Links to Component Definitions (CDEFs) via the
BoundaryCdefDocumentjoin table.
ProjectMembership records assign team members to projects with specific roles:
| Role Key | Display Label |
|---|---|
authorizing_official |
Authorizing Official (AO) |
system_owner |
System Owner (SO/ISO) |
ciso |
CISO |
isso |
ISSO |
project_member |
Project Member |
assessor |
Assessor / 3PAO |
view_only |
View Only |
Memberships can be linked to User records by matching email via link_to_user!.
- PR #71 (Issue #46)
Models: app/models/audit_event.rb
Concern: app/controllers/concerns/auditable.rb
Export: app/services/audit_csv_export_service.rb
Admin UI: app/controllers/admin/audit_logs_controller.rb
-
Immutable --
AuditEventhas noupdated_atcolumn and no update methods. Records are write-once. -
Subject tracking -- polymorphic
subject_type/subject_idcolumns record which resource was affected. -
Structured JSON logging -- every event is also emitted to
Rails.logger.infoas structured JSON for integration with CloudWatch, Datadog, or any log aggregator.
Approximately 80 actions are tracked across 16 categories:
| Category | Example Actions |
|---|---|
| Authentication |
login_success, login_failure, logout, password_change
|
| Authorization | authorization_failure |
| User Management |
user_suspended, user_reactivated, admin_bootstrap
|
| Role Management |
role_grant, role_revoke, role_created, role_updated, role_deleted
|
| Project Members |
project_member_added, project_member_removed, project_membership_*
|
| SSP Documents |
ssp_document_created, _updated, _deleted, _exported, _imported
|
| SAR Documents |
sar_document_created, _updated, _deleted, _exported, _imported
|
| CDEF Documents |
cdef_document_created, _updated, _deleted, _exported, _copied
|
| SAP Documents |
sap_document_created, _updated, _deleted, _exported, _imported
|
| POAM Documents |
poam_document_created, _updated, _deleted, _exported, poam_item_*
|
| Profiles |
profile_document_created, _updated, _deleted, _exported, _copied
|
| Control Catalogs |
control_catalog_*, control_family_*, catalog_control_*
|
| Control Mappings |
control_mapping_*, mapping_entry_*
|
| Evidence |
evidence_created, _updated, _deleted, attestation_created, _deleted
|
| Projects |
project_created, _updated, _deleted, boundary_*
|
Controllers include the Auditable concern for a DRY logging helper that automatically captures the current user, IP address, and user agent:
audit_log("ssp_document_created", subject: @ssp_document,
metadata: { name: @ssp_document.name, file_type: file_type })Authorization failures are logged automatically.
The AuditEvent.log class method creates the record and emits the structured JSON log:
AuditEvent.log(
user: current_user,
action: "login_success",
provider: "local",
ip_address: request.remote_ip,
subject: @ssp_document
)The admin audit log interface is available at /admin/audit_logs and supports:
- Filtering by user, action, subject type, category, date range, and free-text search.
- Pagination at 50 events per page via Pagy.
-
CSV export up to 10,000 rows via
AuditCsvExportService, with columns: timestamp, user_email, action, category, subject_type, subject_id, ip_address, user_agent, metadata. - Detail view for individual events.
- PR #121, PR #122 (Issue #101)
Stimulus Controller: app/javascript/controllers/heatmap_controller.js
Aggregation Service: app/services/dashboard_aggregation_service.rb
Interactive heatmap grids provide visual analytics showing control distribution by NIST family and a secondary dimension (status, severity, result, or priority).
| Document Type | Secondary Dimension | Example Values |
|---|---|---|
| SSP | Status | Implemented, Deferred, Not Applicable, Planned, Partially Implemented |
| SAR | Result | Pass, Fail |
| CDEF | Severity | High, Medium, Low |
| Profile | Priority | P1, P2, P3 |
| Dashboard | Status (aggregate) | Compliance across all SSP documents |
The DashboardAggregationService aggregates implementation status counts across ALL SSP documents, grouped by NIST control family (AC, AU, CM, IA, etc.). It returns a triple of [heatmap_data, families, ordered_statuses] matching the contract expected by the shared _heatmap.html.erb partial.
The heatmap_controller.js Stimulus controller provides client-side interactivity:
-
Cell click (
filterByCell) -- filters controls by both family and status/severity. -
Family click (
filterByFamily) -- filters controls by family only. -
Chip click (
filterByChip) -- filters by status/severity only. - Clear -- resets all filters.
- Visual feedback -- active cells are highlighted with an outline; inactive cells fade to 35% opacity.
- Banner -- displays "Showing: AC . Implemented -- 12 control(s)" when a filter is active.
-
URL sync -- optionally pushes filter state into the URL via
history.replaceState. - Keyboard navigation -- Enter/Space activates cells; Escape clears the filter.
-
ARIA support -- updates
aria-pressedstates on badge elements.
Configuration values:
-
filterKey--"status"or"severity"(the secondary dimension). -
initialFamily/initialFilter-- pre-applied filters from URL parameters. -
urlSync-- boolean to enable URL synchronization. -
containerId-- ID of the controls container element (default:"controlsContainer").
- Issue #81 (PR #82), Issue #83 (PR #84)
File: app/services/document_duplication_service.rb
The DocumentDuplicationService clones ProfileDocument and CdefDocument records with all their associated controls and fields.
SUPPORTED_TYPES = {
"ProfileDocument" => { controls: :profile_controls, fields: :profile_control_fields, version_attr: :profile_version },
"CdefDocument" => { controls: :cdef_controls, fields: :cdef_control_fields, version_attr: :cdef_version }
}-
Build document copy -- copies all attributes except
id, timestamps,status,error_message,original_filename,file_type. Setsstatus: "completed". -
Set import_metadata -- records
copied_from(source document ID) andcopied_at(timestamp) for provenance tracking. - Copy controls -- iterates through source controls and their fields, creating new records linked to the copy.
- All operations run within a single transaction.
service = DocumentDuplicationService.new(source_profile)
copy = service.duplicate(new_name: "FY26 Baseline")
copy.import_metadata
# => { "copied_from" => 42, "copied_at" => "2026-03-08T..." }Profiles can also be created from catalog control selection, producing a new ProfileDocument with selected controls.
- PR #76 (Issue #56)
File: app/services/sap_generator_service.rb
The SapGeneratorService generates a Security Assessment Plan (SAP) from existing SSP and/or Profile data.
SapGeneratorService.new(
name: "FY26 Annual Assessment",
ssp_document: ssp,
profile_document: profile,
assessment_type: "annual",
assessment_start: Date.today,
assessment_end: Date.today + 30,
selected_control_ids: ["AC-1", "AC-2"],
assessment_methods: { "AC-1" => "examine", "AC-2" => "test" }
).generateControls are gathered from the SSP (preferred) or Profile (fallback). When using an SSP, implementation status and description are carried forward.
Each control is assigned a default assessment method based on its family:
| Method | Families |
|---|---|
| Test | AC, AU, CM, IA, SC, SI |
| Interview | AT, PS, PE |
| Examine | All other families (CP, IR, MA, etc.) |
The service enriches generated controls with data from two sources:
-
Catalog guidance -- looks up
CatalogControlrecords to populate assessment objectives and descriptions. -
CDEF test mappings -- looks up
CdefControlrecords withcheck_contentfields. If a control has an "examine" method but has available check content from a CDEF, the method is upgraded to "test".
Creates a SapDocument with status: "completed", linked SapControl records (each with assessment method, status "planned", objective, and test case), and associated SapControlField records for implementation details.
- Issue #28
File: app/services/json_export_service.rb
The JsonExportService provides universal JSON export for all six document types. It delegates to each model's to_json_data method:
JsonExportService.export_ssp(ssp_document)
JsonExportService.export_sar(sar_document)
JsonExportService.export_cdef(cdef_document)
JsonExportService.export_profile(profile_document)
JsonExportService.export_sap(sap_document)
JsonExportService.export_poam(poam_document)Each export produces pretty-printed JSON via JSON.pretty_generate.
File: app/services/sar_excel_export_service.rb
The SarExcelExportService provides round-trip Excel export for SAR documents, preserving the original sheet order and headers from the import. Uses the caxlsx gem to generate .xlsx files:
- Sheet order and header metadata are read from
excel_metadatastored during import. - Falls back to generating sections and headers from the control data if metadata is not available.
- Each sheet gets styled headers and data rows ordered by
row_order. - Excel sheet names are truncated to 31 characters to comply with Excel limits.
Namespace: /api/v1/
Controller: app/controllers/api/v1/ssp_documents_controller.rb
The API provides programmatic access to document operations.
| Endpoint | Method | Description |
|---|---|---|
/api/v1/ssp_documents/convert |
POST | Upload and convert an Excel file |
/api/v1/ssp_documents/:id/update_fields |
PUT | Bulk update control fields |
/api/v1/ssp_documents/:id/export |
GET | Export document as JSON |
Accepts an excel_file parameter. Creates a temporary file, processes it via SspDocument.from_excel, and returns the parsed JSON data along with the document_id.
{
"success": true,
"message": "Conversion successful",
"data": { ... },
"document_id": 42
}Accepts a controls parameter with field updates. Uses SspUpdateService for bulk updates.
Returns the document's JSON representation via JsonExportService.export_ssp.
SAR documents have a parallel set of API endpoints: convert (POST), update_fields (PUT), export (GET).
For web UI uploads, the conversion runs asynchronously via DocumentConversionJob. The convert API endpoint returns a document_id that can be used to poll the status endpoint for completion.
Stimulus Controller: app/javascript/controllers/theme_controller.js
Stylesheet: app/assets/stylesheets/sparc-theme.css
On first visit (no saved preference), the theme follows the OS setting via prefers-color-scheme media query:
const systemDark = window.matchMedia("(prefers-color-scheme: dark)").matches
document.documentElement.setAttribute("data-bs-theme", systemDark ? "dark" : "light")Clicking the theme toggle button saves the preference to localStorage under the sparc-theme key. Once a manual override is set, OS preference changes are ignored:
toggle() {
const current = document.documentElement.getAttribute("data-bs-theme")
const next = current === "dark" ? "light" : "dark"
localStorage.setItem("sparc-theme", next)
document.documentElement.setAttribute("data-bs-theme", next)
}If the user has not manually toggled, OS preference changes are tracked via MediaQueryList.addEventListener("change", ...) and applied automatically.
An inline <script> in the <head> reads localStorage and sets the data-bs-theme attribute on <html> before the first paint, preventing a flash of the wrong theme.
Uses Bootstrap 5.3 color mode support via the data-bs-theme attribute on the root <html> element. Custom SPARC theme overrides are defined in sparc-theme.css for brand-specific colors and component styling.
- Issue #85 (PR #86), Issue #51 (PR #62)
Getting Started
User Guides
- User Guides (index)
- Getting Oriented
- Authorization Boundaries
- Control Catalogs & Baselines
- Converters & Imports
- System Security Plans (SSP)
- Component Definitions (CDEF)
- Security Assessment Plan (SAP)
- Security Assessment Results (SAR)
- POA&M
- Evidence & Attestations
- HDF Amendment Triage
- Compliance Library
- Security Keys & Smart Cards
- Administration
Documentation
- RBAC (Role-Based Access Control)
- Data Isolation
- Screens & UI
- Core Functions & Features
- Framework Mapping
- Integrations
- Architecture
- API Reference
Reference
Links