DRAFT (do not merge): CI validation — remove component sync/merge engine from #731#741
Closed
wdower wants to merge 601 commits into
Closed
DRAFT (do not merge): CI validation — remove component sync/merge engine from #731#741wdower wants to merge 601 commits into
wdower wants to merge 601 commits into
Conversation
rule_satisfactions was created in 20211103190520 as a HABTM join table with no DB-level foreign key constraints on either rule_id or satisfied_by_rule_id. Both reference base_rules.id. Without FKs, a satisfaction row pointing at a non-existent rule on either side persists as a dangling bigint — silently mis-routed during component sync merge (expert review finding F2 / PRE-PHASE-1 BLOCKER for v2-480.1). Applies the codebase's Strong Migrations 2-pass pattern (mirrors 20260502150000 / 20260502150001 for reviews.rule_id): Pass 1 (20260605142102): add_foreign_key with validate: false on both columns. Constraint exists for new writes; existing rows not scanned under ACCESS EXCLUSIVE. Pass 2 (20260605142103): disable_ddl_transaction!, delete orphan rows (any satisfaction whose rule_id or satisfied_by_rule_id references a non-existent base_rule), then validate_foreign_key. Validation does not hold the long lock; orphan cleanup is defensive against non-Rails operations (manual SQL, prior bug) — no orphans should exist on a clean instance because Rule's HABTM cleanup runs as part of dependent: :destroy. on_delete: :cascade (different from reviews — vulcan-cascade-rails-owns memory does NOT apply here): rule_satisfactions has no Rails callbacks to preserve (the model is an empty RuleSatisfaction < ApplicationRecord stub), no audited gem hooks, and the relation is purely binary — when either parent rule disappears, the row is meaningless and should be cleaned up at the DB level too. Belt and suspenders alongside HABTM's own dependent cleanup. spec/migrations/rule_satisfactions_fk_two_pass_spec.rb encodes the end-state invariant: both FKs exist, target base_rules, use :cascade, and are validated in Postgres (convalidated = true). 8/8 green. Adjacent FK regression spec (responding_to_fk_two_pass) and the rule_satisfactions request spec stay green (14/14). Refs: v2-480.13 (PRE-PHASE-1 BLOCKER for v2-480.1), expert review finding F2. Signed-off-by: Will Dower <will@dower.dev>
- UserBlueprint :admin view replaces USER_JSON_FIELDS constant + all .as_json calls in UsersController (5 sites) and ApplicationController locked_users - UserBlueprint :profile view for Devise registration pages (6 fields vs 10) - ProjectBlueprint replaces @project.to_json in rules/index.html.haml - users/index.html.haml uses UserBlueprint :admin instead of .as_json - OpenAPI UserSummary schema updated (Blueprinter timestamp format) - Over-exposure eliminated: uid, unique_session_id, admin flag removed from profile page DOM; created_at, updated_at removed from all HAML injections Authored by: Aaron Lippold<lippold@gmail.com>
- POST /rules/:rule_id/comments route removed (CommentsController doesn't exist) - 6 Jbuilder templates deleted (components, stigs, srgs index/show) — all controllers use Blueprint render body: for JSON, Jbuilder never reached - Routing spec added to verify dead route is gone Authored by: Aaron Lippold<lippold@gmail.com>
- Unlink: Devise valid_for_authentication? for lockout (3 attempts → lock), resets failed_attempts on successful unlink - POST /users/initiate_link: session-flag flow for linking external provider to local account. OmniAuth callback detects link_in_progress + current_user and attaches identity. Edge case: identity already linked → error. - Settings.respond_to? guard for unconfigured providers (github) - 8 ADNM rake task tests (dry run, live run, idempotency, filters) - 3 lockout tests, 5 initiate_link tests Authored by: Aaron Lippold<lippold@gmail.com>
- Turbolinks gem + npm packages removed - All 24 packs: turbolinks:load → DOMContentLoaded - vue-turbolinks adapter removed from createVulcanApp + inline packs - data-turbolinks attributes removed from 5 Vue components + 2 HAML - vue-router@3.6.5 installed (hash mode, Vue 2.7 compatible) - createVulcanApp accepts optional router param - 3 router configs: componentEditor, triagePage, defaultRoutes - rules.js, component_triage.js, project_triage.js → createVulcanApp Authored by: Aaron Lippold<lippold@gmail.com>
- Pinia Setup Store replaces useRuleSelection composable - store.selectRule() calls router.push (hash mode) - 8 components migrated: RuleNavigator, RuleSatisfactions, NewRuleModalForm, SatisfiedByIndicator, RuleEditorHeader, ControlsSidepanels, RulesCodeEditorView, ProjectComponent - Zero @ruleSelected event emissions remain in editor - selectedRuleId/openRuleIds prop drilling removed - SatisfiedByIndicator "Go to parent" wired to store - routerTestHelper: abstract mode for jsdom compatibility - getFirstVisibleRule extracted to utils/ruleSelectionUtils.js - useRuleSelection.js + SelectedRulesMixin.vue deleted Authored by: Aaron Lippold<lippold@gmail.com>
Authored by: Aaron Lippold<lippold@gmail.com>
- Additive filter model (all-unchecked = show all) - Guard clause in useRuleFilters for all-unchecked case - Count indicator "All Rules (X of Y)" + hasActiveFilters - ActiveFilterPills with dismiss + clear all - Open Comments Only moved to FilterBar Display section - Status filters reordered to lifecycle (NYD first) - Fixed clearFilters mutation bug - Changed "reset" to "clear" label Authored by: Aaron Lippold<lippold@gmail.com>
- RuleNavigator 906→293 lines (thin orchestrator) - Created RuleSearchBar (search + modal + clear) - Created RuleList (open rules + all rules + nesting) - Created RuleRowIcons (DRY icon pattern, fixed broken <i>) - Created AlsoSatisfiesModal (from RulesCodeEditorView) - Extracted searchHighlight.js utility - Removed dead ruleStatusCounts computed - Removed setInterval localStorage polling hack Authored by: Aaron Lippold<lippold@gmail.com>
- body: vh-100 d-flex flex-column overflow-hidden - container-fluid: flex-grow-1 overflow-auto (list pages) - .vulcan-editor-layout utility class in design system - PanelLayout: b-card no-body wrapper (border + radius) - ControlsPageLayout: header/body slot pass-through - HAML wrappers: .vulcan-editor-layout on editor pages - Empty state: icon + centered text Authored by: Aaron Lippold<lippold@gmail.com>
- useRuleNavigation composable (filter/search pipeline) - RulesCodeEditorView: header/body slot split for sidebar - ProjectComponent: same header/body slot split - RuleEditor: toolbar flex-shrink-0, content overflow-auto - Flattened nav composable returns for Vue 2 auto-unwrap Authored by: Aaron Lippold<lippold@gmail.com>
Replaces the full-width CommentPeriodBanner with a compact button chip for the command bar. Shows phase status (open/closed), days remaining, and pending comment count badge. Clickable to open comments panel. 11 tests. Authored by: Aaron Lippold<lippold@gmail.com>
Move breadcrumbs from standalone row into command bar. Remove CommentPeriodBanner from page layout (replaced by CommentStatusChip). Make filter bar toggleable with localStorage persistence. Add activeFilterCount to useRuleFilters composable (individual filter count, not categories). Wire both ProjectComponent and RulesCodeEditorView. BaseCommandBar gains #above slot. Authored by: Aaron Lippold<lippold@gmail.com>
Reorganize ControlsCommandBar into 3 semantic zones separated by border dividers: actions (Edit, Comment), status (CommentStatusChip), view controls (Filters toggle in panel row). Rare actions (Download, Upload, Release) move to overflow dropdown with tooltip. Add Clear Filters button when filters active. Add discoverability tooltips to all command bar buttons via v-b-tooltip.hover. Authored by: Aaron Lippold<lippold@gmail.com>
Add v-b-tooltip.hover to all INFO and ACTIONS row buttons: Related, Satisfies, History, Comment History, Comment, DISA Guide, Change Review Status, Clone, Delete. Comment button upgraded from native title to BootstrapVue tooltip for consistent behavior. 9 tests. Authored by: Aaron Lippold<lippold@gmail.com>
Add buttonTooltip prop to CommentModal for caller-provided tooltip text. Wire Save, Lock, Unlock tooltips in RuleActionsToolbar. Add v-b-tooltip.hover to all INFO/ACTIONS buttons (Related, Satisfies, History, Comment History, DISA Guide, Change Review Status, Clone, Delete). Change Lock variant from outline-dark to outline-secondary for better visibility in dark mode. Authored by: Aaron Lippold<lippold@gmail.com>
Rules.vue had a plain <div> root element that broke the CSS flexbox chain. Per spec §4.5, overflow:hidden resets min-height:auto to 0 at each chain link — one missing link and all downstream scroll containers expand to content height. Both sidebar and content area lost scrolling in edit mode. Add regression tests on all 3 chain components (Rules.vue, ProjectComponent.vue, ControlsPageLayout.vue). Update SCSS docs to mark the chain as MANDATORY. Authored by: Aaron Lippold<lippold@gmail.com>
Consolidate 6 individual .btn-outline-* blocks and 5 individual .badge-* blocks into ONE @each loop using $dark-variants map. Formulas match Bootstrap 5.3: tint-color(40%) for text, shade-color (60%) for bg. Add regression tests in design_system_audit_spec.rb that fail if individual variant blocks are reintroduced. Document the pattern in design-system.md. Update SCSS chain docs. Authored by: Aaron Lippold<lippold@gmail.com>
Authored by: Aaron Lippold<lippold@gmail.com>
- Remove @ivar aliases from reviews_model_base and components_model_base; specs now use let_it_be names directly (reviews_*, components_*) - Remove dead shared_p2 from reviews_model_base (zero consumers) - Membership.create → create! to surface silent setup failures - Project.create → create(:project) for FactoryBot consistency - Rule.create → create! for explicit error surfacing - Prefix let_it_be names to prevent collision: reviews_srg vs components_srg - Rename context 'reviews base setup' → 'reviews request base setup' - Promote let(:rule) → let_it_be(:rule) in request context - Keep anchor_admin in request context — absorbs first-user-admin promotion (Settings.admin_bootstrap.first_user_admin defaults true), preventing test users from accidentally becoming admin and bypassing role checks 399 examples, 0 failures across 29 affected spec files Authored by: Aaron Lippold<lippold@gmail.com>
- components_counts_spec: Audited.auditing_enabled = false (global)
replaced with Component.without_auditing { Rule.without_auditing { } }
(per-model scoped, ensure-based cleanup)
- users.rb factory: before/after callback split replaced with to_create
wrapping save! in User.without_auditing { } — ensure-based cleanup
even if save! raises (fragile split pattern could leave auditing
disabled on factory create failure)
Zero Audited.auditing_enabled mutations remain in spec/
Authored by: Aaron Lippold<lippold@gmail.com>
SatisfiedByBlueprint includes component_prefix (so frontend renders "Satisfied by PREFIX-RULE_ID" without extra lookups). Test expectation was written before that field was added. Authored by: Aaron Lippold<lippold@gmail.com>
- Removed include_context 'components model base setup' (604KB SRG parse + ~250 rule import per test group for zero benefit) - Used :skip_rules trait on create(:component) since comment_phase tests only exercise attribute validations, not rules - 17.43s → 12.58s (28% faster) Authored by: Aaron Lippold<lippold@gmail.com>
Both specs test ComponentsController actions (POST /components/:id/lock, PATCH /components/:component_id/lock_sections), not ReviewsController. Neither included the reviews shared context. Authored by: Aaron Lippold<lippold@gmail.com>
- :closed_comment_phase trait on components factory (Analyzer precondition)
- JsonArchiveArchiveBuilder helper produces backup-shape hashes from factory
records (no checked-in zip fixtures)
- Auto-included in spec/services/import/{json_archive/,}merge/ + sync rake specs
Refs v2-480.1 commit 1 of 11 in
docs/superpowers/plans/2026-06-04-v2-480.1-implementation-plan.md.
Signed-off-by: Will Dower <will@dower.dev>
Pure-Ruby matcher: groups two sets of review hashes by (rule_id, created_at, comment_digest) and partitions matched / only_ours / only_theirs. Degenerate same-key groups tiebreak by external_id position and are recorded in a separate collisions array for MergePlan warnings. - digest: SHA-256[0..15] of NFC-normalized comment text (64 bits) - v1.0 manifest fallback truncates created_at to second precision for legacy archives that pre-date the iso8601(6) BackupSerializer fix - No composite index migration this commit — Phase 1 matcher is in-memory; index would only pay off for the Phase 2 applier (per expert review F21) Refs commit 2 of 11 in docs/superpowers/plans/2026-06-04-v2-480.1-implementation-plan.md. Signed-off-by: Will Dower <will@dower.dev>
Class (not Struct) per expert review F19/F20 — construction validates format and shape so bad inputs fail at the boundary. - .from_json_archive pins to the single-component slice and raises ArgumentError when component is missing (F20: stop the silent-drop of satisfactions.json on flat archives) - .from_spreadsheet applies SpreadsheetParser.normalize_header_aliases so DISA-format headers map to import headers (F18) - .from_zip_path detects flat vs nested layout; multi-component archives must pass component_dir: to disambiguate - nil arrays coerced to empty; symbol-keyed hashes deep_stringify'd Refs commit 3 of 11 in docs/superpowers/plans/2026-06-04-v2-480.1-implementation-plan.md. Signed-off-by: Will Dower <will@dower.dev>
Strategy resolves "what wins when both sides diverge on a field." Seven verbs: :ours, :theirs, :newer, :conflict, :union, :skip, :manual. Defaults favor conservatism on rule content (:conflict, force human review) and additivity on memberships / satisfactions (:union). - VALID_STRATEGY_RESOLUTIONS constant (frozen) — analyzer + tests share the canonical list (F19) - DEFAULT_STRATEGY (frozen) declares per-entity policy with _default sentinel keys for blanket fallbacks - #locked_field_resolution always returns :conflict; locked fields cannot be overridden by any strategy - ArgumentError on unknown resolution at construction (fail at boundary) - from_cli_flags intentionally NOT here — belongs in the rake layer (F18) Refs commit 4 of 11 in docs/superpowers/plans/2026-06-04-v2-480.1-implementation-plan.md. Signed-off-by: Will Dower <will@dower.dev>
Three parallel lists (RuleBuilder DIRECT_COLUMNS, BackupSerializer EXCLUDED_RULE_COLUMNS by inverse, round-trip RULE_COMPARE_FIELDS) had drifted apart. Per expert review F19, consolidate on Rule::MERGEABLE_FIELDS + Rule::DERIVED_COLUMNS as the canonical content set; everything else derives. - Rule::MERGEABLE_FIELDS (19 content fields) and DERIVED_COLUMNS (inspec_control_file — regenerated by update_inspec_code after_save) - RuleBuilder::DIRECT_COLUMNS now = MERGEABLE + identity + lifecycle + DERIVED (same 25 columns as before, no drift) - BackupSerializer EXCLUDED_RULE_COLUMNS gets a cross-reference comment pointing at the canonical lists - RULE_COMPARE_FIELDS in the round-trip spec now derives from MERGEABLE_FIELDS + :locked - New contract spec asserts MERGEABLE ⊆ valid columns, no MERGEABLE/ DERIVED overlap, DIRECT_COLUMNS is the exact union Round-trip spec still 24/0 green — no observable behavior change. Refs commit 5 of 11 in docs/superpowers/plans/2026-06-04-v2-480.1-implementation-plan.md. Signed-off-by: Will Dower <will@dower.dev>
Five new path files documenting the auth flow endpoints the SPA needs: POST/PUT /users/password, GET /users/password/edit, POST /users/confirmation, POST /users/unlock, GET /users/edit. Toast endpoints reference ToastResponse; profile references CurrentUserResponse; token validation documents its inline shape. 12 contract tests validate real responses against the bundled schema via openapi_first, including paranoid-mode success responses and 422 error shapes. Live-tested against the running dev server with curl — all request/response pairs verified with real data. Authored by: Aaron Lippold<lippold@gmail.com>
bundler-audit flagged patch-level CVEs in transitive dependencies: concurrent-ruby, crass, faraday, json, msgpack, net-imap, nokogiri, oauth2, oj, websocket-driver. Conservative update — no version pin changes in Gemfile. bundler-audit now clean; full suite green (3851 RSpec / 3357 Vitest). Authored by: Aaron Lippold<lippold@gmail.com>
helpful_errors rescued StandardError and rendered a generic 500 toast with no logging — the root cause of any production 500 was invisible everywhere. Log the exception class, message, and top of the backtrace before rendering. Specs cover the log line and the admin/non-admin message split. Authored by: Aaron Lippold<lippold@gmail.com>
devise-security's before_logout hook calls update_unique_session_id! during the sign_out that follows account self-deletion; on the destroyed record it raises NotPersistedError. The account deleted but the user saw a 500 toast (session limits are enabled by default). The hook respects skip_session_limitable? — return true for destroyed records. Covered by the self-delete request specs and live-verified. Authored by: Aaron Lippold<lippold@gmail.com>
Self-delete previously took one click on a live session — no password, no continuity check. Per OWASP ASVS 3.7.1 and the GitLab/GitHub account deletion pattern: - Local-credential users must re-enter current_password; wrong attempts count toward lockout (423 once locked). Provider-managed and SSO-created (auto-password) accounts are exempt — their IdP owns re-authentication. - The last system admin cannot self-delete (mirrors the existing UsersController guard): 422 with promote-another-admin guidance. - ConfirmDeleteModal gains an optional requirePassword prop (default false, other consumers unaffected); the profile delete modal collects the password for local users only. - DELETE /users documented in OpenAPI (200/422/423) with contract tests validating each response shape against the schema. Live-verified: curl matrix with DB checks + Playwright golden path in light and dark modes. Authored by: Aaron Lippold<lippold@gmail.com>
A project could drop to zero admins through the membership API, orphaning it for its team. Per the GitLab/GitHub last-owner pattern, the last project-admin membership can no longer be removed or downgraded — the 422 names the project and says to transfer the admin role first. The guard lives in the model as the single source of truth (update validation + before_destroy abort), so console, importers, and any future callers hit it too; the existing controller error paths surface it with no controller changes. Cascades are exempt via destroyed_by_association (deleting a project legitimately removes its memberships; the user-deletion path gets its own guard next). Component memberships are exempt — project admins govern components through effective_permissions. The OpenAPI spec had promised 'cannot demote the last admin' since it was authored; the 422 responses are now documented and the code honors the design. 13 new specs + schema-validated contract tests; live verified via curl with the allow-path confirmed after adding a second admin. Authored by: Aaron Lippold<lippold@gmail.com>
Deleting a user who held a project's only admin membership silently
orphaned the project for its team — the membership cascade bypasses the
membership-layer guard by design (destroyed_by_association). Apply the
GitLab/GitHub transfer-ownership rule at both deletion endpoints: the
admin user-management path and Devise self-delete return 422 naming the
affected projects until the admin role is transferred.
User#solely_administered_projects is the shared source of truth — one
SQL query (admin-membership subselect anti-joined against other-admin
memberships), no Ruby enumeration; component memberships never count
since project admins govern components through effective_permissions.
The block message comes from a single model method with a contextual
subject ('This user is' / 'You are'), capped at five project names with
an overflow count. System admins get no bypass — continuity, not
privilege.
OpenAPI 422s documented on both delete operations with contract tests
validating each against the schema. Live-verified: both paths blocked,
then allowed after transferring the role to a second admin.
Authored by: Aaron Lippold<lippold@gmail.com>
- create now respond_to json: canonical toast + new request id (200) or danger toast on duplicate request (422); HTML flash unchanged - Fix destroy success toast title: was 'Access request submitted.' on deny/cancel — now 'Access request denied.'/'... cancelled.' - destroy converted from hand-built Toast.new to render_toast helper - OpenAPI POST path rewritten from '302 HTML-only' to the JSON contract; schema renamed AccessRequestToastResponse (POST + DELETE) - Bundle regenerated (includes the latest-benchmark paths committed with their sources in the next commit — hook requires current bundle) - 4 new request specs + 2 contract tests; live curl verified Authored by: Aaron Lippold<lippold@gmail.com>
One centralized implementation serves three family-grouped listings
for SPA dropdown population:
- GET /api/srgs/latest + /api/stigs/latest — public DISA reference
data, one family per row at highest numeric V{major}R{minor}
- GET /api/components/latest — released components only (a component
is a STIG in progress), auth required, family = prefix, ranked on
integer version/release with NULLS LAST; drafts never shadow
- LatestBenchmarkListable concern: single #latest action, per-
controller declarative config, latest_base_scope visibility seam
- BenchmarkSearchable concern: SearchQueryService-backed ?q= across
all three models (abbreviation expansion, parameterized ILIKE)
- GPOS added to core search abbreviations (helps global search too)
- Lean :latest blueprint views — identity fields only, never XML,
no per-record severity/currency queries
- 2 OpenAPI schemas + 3 path files + contract tests (incl. 401 shape)
- 27 new examples, all RED-first; authz coverage entries documented
Authored by: Aaron Lippold<lippold@gmail.com>
- GET /api/srgs/:id/stats (authenticated): rule count + severity breakdown as SQL aggregates, plus usage — components based on the SRG, scoped to caller-visible projects (member/discoverable or released); count and list share one scope so hidden usage never leaks through the count - GET /api/stigs/:id/stats (public): rule count + severity breakdown only — pure reference data, no usage (components are based on SRGs) - SeverityCounts#benchmark_stats: one COUNT + one GROUP BY, no Ruby enumeration; reusable by Component for the dashboard stats card - SrgsController skip_before_action narrowed to only: :latest, with the cop-documented lexical anchor for the concern-provided action - OpenAPI BenchmarkStatsResponse schema + 2 path files; 4 contract tests incl. 401/404 shapes; authz coverage entries documented - SRG factory :skip_rules trait (mirrors stig factory) so tests hand-craft severities; live-tested incl. non-member data-leak check Authored by: Aaron Lippold<lippold@gmail.com>
- GET /api/components/:id/summary — lightweight header for SPA triage/settings routes: identity, counts, srg info, and the caller's effective_permissions; never ships the rules/reviews/ histories arrays (721 B vs 1.06 MB full blob on real data) - Comment-phase state machine serialized from the model methods (accepting_new_comments, triaging_active, frozen_for_writes, comment_period_days_remaining) — single source of truth, clients stop reimplementing the write-guard logic to predict rejections - ComponentBlueprint :summary view — fields live in the view, the column-limited default field list is untouched - authorize_component_access relocated to ApplicationController so the API controller shares the released→logged-in / else→viewer rule with ComponentsController - OpenAPI ComponentSummaryResponse schema + path (200/401/403/404, ISO 8601 date-time verified against live output); 3 contract tests - Exhaustive phase/closed_reason matrix in the request spec; live curl matrix incl. scratch non-viewer 403 proof Authored by: Aaron Lippold<lippold@gmail.com>
- api_projects.yaml: all 5 params (q, sort, order, page, per_page)
with enums matching the controller's apply_sort whitelist and the
pagy defaults (25/page, max 100); 200/400/401 responses
- PaginationMeta.yaml: reusable {page, per_page, total} schema for
the canonical {rows, pagination} envelope — pagination rollout
(v2-btu.8) reuses it instead of inlining per endpoint
- 4 contract tests: default page, q+sort+per_page combined,
400 out-of-range page, 401 unauthenticated
- Timestamps documented as ISO 8601 (render_as_json path) after
live output showed the string-render example format was wrong
for this endpoint
- Zero controller changes — documentation-only card
Authored by: Aaron Lippold<lippold@gmail.com>
Five single-call SQL aggregates powering SPA dashboards: - GET /api/components/:id/stats — rules by status/severity, completion and lock percentages (nil, never fabricated 0.0, on empty components) - GET /api/components/:id/workflow_state — authoring/lock/review/ comment/triage/export readiness, reusing the phase-machine booleans - GET /api/components/:id/triage_summary — top-level comment counts per triage status (all 9 keys zero-filled) + adjudication pct - GET /api/projects/:id/stats — aggregate + per-component breakdown from three grouped queries - GET /api/projects/:id/triage_summary — project-wide triage metrics Supporting model layer: - SeverityCounts-style aggregates live on models: Component# dashboard_stats/#workflow_state, Project#dashboard_stats, Review.triage_summary; Component.status_buckets extracted for reuse - Review.for_components — canonical commentable-based scope mirroring CommentQueryService (rule-attached OR component-attached) - PercentageMath concern: nil on zero denominators Bug fix: Component.pending_comment_counts undercounted component-level comments (commentable=Component, no rule) — the same triage queue. Rewritten as ONE LEFT JOIN + CASE-grouped query, preserving the single-query guard (deleted-rule exclusion moved into the join since raw joins bypass Rule's default_scope). OpenAPI: 5 paths + 6 schemas (SeverityBuckets, RulesByStatus reusable); 7 contract tests; auth via authorize_component_access / authorize_viewer_project; live-tested on real data with cross-checks. Authored by: Aaron Lippold<lippold@gmail.com>
SRG-writing as a first-class document type (v5). Authored SRG requirements are expanded SrgRules with component linkage; catalog attachment at release. Records 14 design decisions: Moved terminal status + tombstone semantics, multi-parent lineage for SRG and STIG components (core vs derived eligibility), family technology token with ID minting at release, relocation marker + dual intake, reference benchmarks, mixed-project sections, full XCCDF export. Includes 7-phase implementation plan. Authored by: Aaron Lippold<lippold@gmail.com>
be_within(5.seconds) of a pre-call timestamp fails when the example runs slowly under load (observed at 6.7s on an unbalanced parallel run). The applier stamps last_sync_at with Time.current during the call, so assert it falls between before/after call bounds instead. Authored by: Aaron Lippold<lippold@gmail.com>
Rack::Attack uses fixed 60-second buckets; without freeze_time the requests can split across a bucket boundary on a slow run, so the counter never reaches the limit and the 429 assertion flakes. The reaction-throttling block already used this pattern — apply it to the login and comment-action blocks too. Authored by: Aaron Lippold<lippold@gmail.com>
Extract the component sync & merge engine (epic v2-480) out of PR #731 so the remaining triage/PAT/upgrade/OpenAPI work can merge without it. The engine is unfinished and prod-data-risky; it is preserved intact on branch feat/component-sync-merge and will be reconstructed off post-#731 master. Deleted (branch-only engine, 56 files): - app/services/import/json_archive/merge/ (20 files) + specs - MergeJob + ApplicationJob, ComponentSyncEvent/MergeOperation/MergeQuarantineRecord - lib/tasks/sync.rake, 7 merge migrations, merge OpenAPI paths/schemas Stripped merge-only hunks from shared files (kept all #731 work): - rule.rb: NESTED_MERGEABLE_ASSOCIATIONS (MERGEABLE_FIELDS/DERIVED_COLUMNS kept — load-bearing for RuleBuilder + backup round-trip) - review.rb: MERGEABLE_FIELDS constant - component.rb: component_sync_events / merge_operations associations - manifest_validator.rb: merge: param + branch (1.1 format support kept) - components_controller.rb: merge_status; projects_controller.rb: import_backup?merge + helpers - routes.rb: merge_status route; vulcan.default.yml: sync: block - db/schema.rb: 3 merge tables, last_sync_* columns, merge FKs - OpenAPI: merge params/paths/schemas removed, bundle regenerated Kept as #731's own work: ReviewBuilder/backup_serializer import hardening, rule_satisfactions FK constraints, backup format 1.1, and comment-merge (Review.merge_comments! / PATCH /reviews/merge). Schema.rb hand-edited (deterministic removal of merge DDL); authoritative db:migrate regen + full rspec run to follow in the split-validation step. Signed-off-by: Will Dower <will@dower.dev>
| auth = request.headers['Authorization'] | ||
| return nil unless auth | ||
|
|
||
| match = auth.match(/\AToken\s+(.+)\z/i) |
Resolve 2 conflicts from master advancing 2 commits (#736 ironbank, #740 gem bumps): release.yml keeps both the openapi-publish job (#731) and the Iron Bank release steps (master); Gemfile.lock takes master's security bumps reconciled with #731's gems via bundle install. Unblocks pull_request CI on the removal branch (was CONFLICTING with master). Signed-off-by: Will Dower <will@dower.dev>
| target: production | ||
| environment: | ||
| RAILS_ENV: production | ||
| DATABASE_URL: postgres://postgres:schemathesis@db/vulcan_schemathesis |
| @@ -39,6 +39,11 @@ | |||
| default: false, | |||
| }, | |||
| }, | |||
| data() { | |||
| return { | |||
| instanceId: Math.random().toString(36).slice(2, 9), | |||
| // picks its own seed, so popoverIds don't collide when (e.g.) the navbar | ||
| // pack and the project_component pack both render UserBadges on the same | ||
| // page. Combined with this._uid for per-instance uniqueness within a pack. | ||
| const moduleSeed = Math.random().toString(36).slice(2, 8); |
| name: Jane Doe | ||
| email: jane.doe@example.org | ||
| admin: false | ||
| password: SecureP@ssw0rd2026! |
| summary: Set a new password | ||
| value: | ||
| user: | ||
| password: SecureP@ssw0rd2026! |
|
|
||
| it 'returns true when IP is within an allowed CIDR' do | ||
| token = create(:personal_access_token, user: user, | ||
| allowed_ips: ['10.0.0.0/8']) |
|
|
||
| it 'returns true when allowed_ips is empty array (allow all)' do | ||
| token = create(:personal_access_token, user: user, allowed_ips: []) | ||
| expect(token.ip_allowed?('1.2.3.4')).to be true |
| describe '#ip_allowed?' do | ||
| it 'returns true when allowed_ips is nil (allow all)' do | ||
| token = create(:personal_access_token, user: user, allowed_ips: nil) | ||
| expect(token.ip_allowed?('1.2.3.4')).to be true |
|
|
||
| it 'accepts valid CIDR entries' do | ||
| token = build(:personal_access_token, user: user, | ||
| allowed_ips: ['10.0.0.0/8', '192.168.1.0/24', '2001:db8::/32']) |
|
|
||
| it 'accepts valid CIDR entries' do | ||
| token = build(:personal_access_token, user: user, | ||
| allowed_ips: ['10.0.0.0/8', '192.168.1.0/24', '2001:db8::/32']) |
Two CI failures on the merge-removal branch, both my own, neither from the engine removal itself: 1. Gemfile.lock: resolving the master merge with --theirs downgraded rack 3.2.6 -> 2.2.23 and concurrent-ruby 1.3.7 -> 1.3.6. #731 is ahead of master on gems (Rails 8 / rack 3). The rack 2.x downgrade broke :unprocessable_content, a rack 3.1+ status symbol, failing every spec asserting it; the concurrent-ruby downgrade re-introduced an advisory #731 had already fixed, failing bundle-audit. Restored #731's lock (rack 3.2.6, concurrent-ruby 1.3.7). 2. manifest_validator_spec: removing the merge: kwarg left keeper specs asserting merge-mode warnings and @merge. Dropped those examples; they belong with the merge PR. Signed-off-by: Will Dower <will@dower.dev>
040d1a9 to
7ef2830
Compare
|
wdower
added a commit
that referenced
this pull request
Jul 12, 2026
Removes the component sync & merge engine (epic v2-480) from this PR so the remaining triage / PAT / upgrade / OpenAPI work can ship without it. The engine is unfinished (UI, disaster-recovery, integration tests and manifest v1.1 all still open) and carries production-data risk, so it is being landed as its own PR rather than holding this one up. The engine is preserved intact on branch feat/component-sync-merge (also pushed to origin). After this PR merges to master, that branch rebases onto the new master and reduces to the engine alone — which is why this PR should land as a merge commit, NOT a squash: a squash breaks that rebase. Removed (56 files): app/services/import/json_archive/merge/ + specs, MergeJob + ApplicationJob, ComponentSyncEvent / MergeOperation / MergeQuarantineRecord, lib/tasks/sync.rake, 7 merge migrations, merge OpenAPI paths and schemas. Stripped merge-only hunks from 12 shared files, keeping all of this PR's own work: ReviewBuilder / backup_serializer import hardening, Rule::MERGEABLE_FIELDS + DERIVED_COLUMNS (load-bearing for RuleBuilder and the backup round-trip), rule_satisfactions FK constraints, backup format 1.1, and comment-merge (Review.merge_comments! / PATCH /reviews/merge — unrelated to the component merge engine). Also merges origin/master, resolving this PR's conflict with master (#736 ironbank workflow, #740 gem bumps). release.yml keeps both the openapi-publish job and the Iron Bank release steps; Gemfile.lock keeps this PR's rack 3.2.6 / concurrent-ruby 1.3.7, which are ahead of master. Validated on CI via draft PR #741: the removal itself produced zero failures (lint, frontend, docker and 4 of 6 backend shards green; the one remaining failure is a pre-existing OIDC login-button spec that the removal cannot touch — it changes no auth file and the oidc config is byte-identical). Tracking: v2-cwy5 (split epic) — .1 dry-run and .3 classify closed, .4 removal this commit. Signed-off-by: Will Dower <will@dower.dev>
Contributor
Author
|
Superseded: the removal has been merged into #731 (merge commit 43e5921). This draft existed only to CI-validate the merge-engine removal, which it did — the removal produced zero CI failures. The engine itself is preserved on |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


This draft exists only to run CI on the component sync/merge engine removal
before it lands on #731. It is based on
feat/comment-triage-context-panel(#731), so the diff below shows all of #731 plus the one removal commit
(
refactor: Remove component sync/merge engine from #731). Review only thatcommit; everything else is #731.
What this validates
The removal extracts epic v2-480 (component sync & merge — the "reupload a
backup to overwrite the DB with 3-way-merged updates" engine) out of #731 so
the rest of #731 can ship. The engine is preserved intact on
feat/component-sync-mergeand will be reconstructed off post-#731 master.What to look at in CI
db:schema:loadexercises the hand-editedschema.rb(mergetables +
last_sync_*columns + FKs removed), then fullparallel_rspec.This is the check we most want green.
surface removed; comment-merge (
Review.merge_comments!,PATCH /reviews/merge)and the spreadsheet round-trip are untouched keepers.
Baseline caveat
#731 has no recent clean CI run, so red here isn't necessarily removal-caused —
merge-spec failures simply vanish (those specs are deleted); any new keeper
failure is what matters.
The removal commit at a glance
app/services/import/json_archive/merge/(20 files) + specs,MergeJob/ApplicationJob, 3 merge models,
sync.rake, 7 merge migrations,merge OpenAPI paths/schemas (56 files)
merge_status,import_backup?merge, routes,sync:settings, schema, OpenAPI bundle)rule_satisfactionsFK constraints, backup format 1.1, comment-merge
Tracking: split epic + children (dry-run, classify, remove, validate, rebase)
staged in beads (pending the shared-DB migration).