rosett-ai 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.ai-provenance.yml +119 -0
- data/.debride_whitelist +186 -0
- data/.fasterer.yml +29 -0
- data/.mdl_style.rb +10 -0
- data/.mdlrc +3 -0
- data/.mutant.yml +49 -0
- data/.namespace-allowlist +42 -0
- data/.reek.yml +1040 -0
- data/.rosett-ai/config.yml +3 -0
- data/.rspec +5 -0
- data/.rubocop.yml +380 -0
- data/.ruby-version +1 -0
- data/.yamllint +51 -0
- data/.yardopts +12 -0
- data/AI-DISCLOSURE.md +48 -0
- data/CHANGELOG.md +519 -0
- data/CLAUDE.md +141 -0
- data/CONTRIBUTING.md +734 -0
- data/INSTALL.md +154 -0
- data/LICENSE +674 -0
- data/LICENSE.md +675 -0
- data/QUICKSTART.md +73 -0
- data/README.md +366 -0
- data/Rakefile +200 -0
- data/SECURITY.md +114 -0
- data/bin/rai +1 -0
- data/cliff.toml +52 -0
- data/conf/adopt_redactions.yml +8 -0
- data/conf/behaviour/.gitkeep +0 -0
- data/conf/compliance/cra_rules.yml +25 -0
- data/conf/compliance/license_rules.yml +20 -0
- data/conf/design/aaif_alignment.yml +181 -0
- data/conf/design/ab_testing.yml +172 -0
- data/conf/design/accessibility.yml +84 -0
- data/conf/design/ai_authorship.yml +210 -0
- data/conf/design/ai_provenance.yml +224 -0
- data/conf/design/ai_tool_configuration.yml +207 -0
- data/conf/design/architecture.yml +139 -0
- data/conf/design/autocompletion.yml +115 -0
- data/conf/design/backward_compatibility.yml +112 -0
- data/conf/design/behaviour_composition.yml +246 -0
- data/conf/design/build_rake_extraction.yml +57 -0
- data/conf/design/ci_pipeline.yml +100 -0
- data/conf/design/claude_code_configuration.yml +157 -0
- data/conf/design/compiler.yml +128 -0
- data/conf/design/comply.yml +153 -0
- data/conf/design/content_packs.yml +84 -0
- data/conf/design/desktop_integration.yml +289 -0
- data/conf/design/distribution.yml +216 -0
- data/conf/design/doctor.yml +184 -0
- data/conf/design/documentation.yml +152 -0
- data/conf/design/engine_architecture.yml +257 -0
- data/conf/design/error_handling.yml +103 -0
- data/conf/design/feature_flags.yml +142 -0
- data/conf/design/git_hooks.yml +165 -0
- data/conf/design/gui_plugins.yml +475 -0
- data/conf/design/i18n.yml +84 -0
- data/conf/design/integration_testing.yml +56 -0
- data/conf/design/licensing_system.yml +88 -0
- data/conf/design/lifecycle_management.yml +208 -0
- data/conf/design/mcp_integration.yml +207 -0
- data/conf/design/mcp_settings.yml +126 -0
- data/conf/design/migration.yml +56 -0
- data/conf/design/monitoring_observability.yml +194 -0
- data/conf/design/namespace_cleanup.yml +145 -0
- data/conf/design/plugin_test_segregation.yml +145 -0
- data/conf/design/policy_management.yml +229 -0
- data/conf/design/project_management.yml +183 -0
- data/conf/design/rai_mcp_asset_discovery.yml +164 -0
- data/conf/design/rai_mcp_server.yml +605 -0
- data/conf/design/release_management.yml +117 -0
- data/conf/design/retrofit.yml +199 -0
- data/conf/design/retrospective_analyzer.yml +79 -0
- data/conf/design/scope_hierarchy.yml +352 -0
- data/conf/design/security.yml +115 -0
- data/conf/design/session_retrospective.yml +85 -0
- data/conf/design/smart_ui_feedback.yml +89 -0
- data/conf/design/structured_logging.yml +148 -0
- data/conf/design/styles.yml +123 -0
- data/conf/design/test_peer_review.yml +89 -0
- data/conf/design/testing.yml +136 -0
- data/conf/design/threat_model.yml +108 -0
- data/conf/design/ui_framework.yml +111 -0
- data/conf/design/usage_optimization.yml +122 -0
- data/conf/design/version_management.yml +60 -0
- data/conf/design/workflow.yml +227 -0
- data/conf/mcp/server_defaults.yml +42 -0
- data/conf/mcp/trust.yml +21 -0
- data/conf/packaging/core.yml +12 -0
- data/conf/packaging/gtk4.yml +11 -0
- data/conf/packaging/qt6.yml +11 -0
- data/conf/policy/default_deny_list.yml +197 -0
- data/conf/review/cli-command-audit.yml +857 -0
- data/conf/review/design-docs.yml +1064 -0
- data/conf/review/design-questionnaire.yml +153 -0
- data/conf/review/questionnaire.yml +146 -0
- data/conf/review/rosett-ai-core.yml +2919 -0
- data/conf/schemas/ai_config_schema.json +73 -0
- data/conf/schemas/behaviour_schema.json +132 -0
- data/conf/schemas/compliance_rule_schema.json +63 -0
- data/conf/schemas/content_pack_manifest_schema.json +51 -0
- data/conf/schemas/design_schema.json +210 -0
- data/conf/schemas/engine_manifest_schema.json +144 -0
- data/conf/schemas/lockfile_schema.json +74 -0
- data/conf/schemas/mcp_server_schema.json +48 -0
- data/conf/schemas/packaging_schema.json +70 -0
- data/conf/schemas/policy_schema.json +85 -0
- data/conf/schemas/provenance_schema.json +84 -0
- data/conf/schemas/rai_config_schema.json +56 -0
- data/conf/schemas/rai_project_schema.json +20 -0
- data/conf/schemas/scope_hierarchy_schema.json +49 -0
- data/conf/schemas/target_schema.json +67 -0
- data/conf/schemas/tooling_schema.json +65 -0
- data/conf/schemas/workflow_schema.json +112 -0
- data/conf/targets/agents_md.yml +17 -0
- data/conf/targets/claude.yml +12 -0
- data/conf/tooling/tools.yml +58 -0
- data/dist/rosett-ai-mcp.service +48 -0
- data/dist/rosett-ai-mcp.yml.default +45 -0
- data/doc/AAIF_POSITIONING.md +58 -0
- data/doc/ADOPT.md +224 -0
- data/doc/AI_PROVENANCE.md +139 -0
- data/doc/ARCHITECTURE.md +920 -0
- data/doc/BEHAVIOUR.md +409 -0
- data/doc/BUILD.md +138 -0
- data/doc/CI_CD_RECIPES.md +171 -0
- data/doc/CLAUDE_SESSIONS_MOVED.md +16 -0
- data/doc/COMMAND_ANALYSIS.md +229 -0
- data/doc/CONFIGURATION.md +281 -0
- data/doc/DESIGN_AUDIT.md +235 -0
- data/doc/DESIGN_PEER_REVIEW.md +771 -0
- data/doc/DESKTOP.md +447 -0
- data/doc/ENGINES.md +567 -0
- data/doc/ENGINE_DEVELOPMENT_GUIDE.md +417 -0
- data/doc/FEATURE_AUDIT.md +218 -0
- data/doc/IMPLEMENTATION_PLAN.md +669 -0
- data/doc/INCIDENT_REPORT_2026-02-02.md +251 -0
- data/doc/MIGRATION_GUIDE.md +88 -0
- data/doc/PACKAGING.md +232 -0
- data/doc/PROJECT_DASHBOARD.md +153 -0
- data/doc/PULP_DEPLOYMENT.md +164 -0
- data/doc/QUALITY_FIX_SUMMARY.md +110 -0
- data/doc/QUICK_START.md +162 -0
- data/doc/REEK_CONFIGURATION.md +166 -0
- data/doc/REFERENCE.md +253 -0
- data/doc/REFERENCES.md +324 -0
- data/doc/SECURITY_REVIEW_CHECKLIST.md +72 -0
- data/doc/SESSION_2026-02-28_GTK4_HARDENING.md +359 -0
- data/doc/SETUP.md +202 -0
- data/doc/TEST_PEER_REVIEW.md +152 -0
- data/doc/THREAT_MODEL.md +230 -0
- data/doc/USAGE.md +545 -0
- data/doc/USER_MANUAL.md +585 -0
- data/doc/ai_test_review_checklist.md +110 -0
- data/doc/changes/2026-02-18-packaging-fpm.md +155 -0
- data/doc/changes/2026-02-19-testing-infrastructure.md +221 -0
- data/doc/changes/2026-02-20-security-implementation.md +281 -0
- data/doc/changes/2026-02-20-styles-implementation.md +220 -0
- data/doc/changes/2026-02-21-architecture-completion.md +95 -0
- data/doc/changes/2026-02-21-architecture-ui-layer.md +253 -0
- data/doc/changes/2026-02-21-cc-config-implementation.md +108 -0
- data/doc/changes/2026-02-21-ci-pipeline-implementation.md +214 -0
- data/doc/changes/2026-02-21-compiler-multi-target-pipeline.md +241 -0
- data/doc/changes/2026-02-21-config-design-show-commands.md +61 -0
- data/doc/changes/2026-02-21-design-implementation-overview.md +455 -0
- data/doc/changes/2026-02-21-lifecycle-management.md +196 -0
- data/doc/changes/2026-02-21-path-resolver.md +128 -0
- data/doc/changes/2026-02-24-ci-tmpdir-mutant-fetch.md +45 -0
- data/doc/changes/2026-03-01-ci-bundler-strategy.md +120 -0
- data/doc/changes/2026-03-20-security-hardening-phase2.md +163 -0
- data/doc/context/SESSION-HANDOFF.md +69 -0
- data/doc/context/ai-engine-usage-trends-2026.md +80 -0
- data/doc/context/plan-pluggable-engines.md +590 -0
- data/doc/decisions/001-flog-deferred.md +32 -0
- data/doc/decisions/002-path-resolution-strategy.md +158 -0
- data/doc/decisions/003-ui-adapter-selection.md +193 -0
- data/doc/decisions/004-design-document-validation.md +179 -0
- data/doc/decisions/005-package-splitting-strategy.md +200 -0
- data/doc/decisions/006-multi-engine-architecture.md +147 -0
- data/doc/decisions/007-engine-agnostic-pivot.md +219 -0
- data/doc/decisions/008-ci-bundler-strategy.md +129 -0
- data/doc/decisions/009-core-only-v1-release.md +60 -0
- data/doc/decisions/010-engine-debian-packaging.md +66 -0
- data/doc/decisions/011-context-aware-cli.md +71 -0
- data/doc/dependency_decisions.yml +247 -0
- data/doc/issues/001-wrapper-missing-environment-variables.md +197 -0
- data/doc/issues/002-embedded-ruby-wrong-prefix.md +217 -0
- data/doc/issues/003-smoke-test-false-positive.md +127 -0
- data/doc/issues/004-market-research-design-updates.md +109 -0
- data/doc/issues/005-compile-scope-coexistence.md +161 -0
- data/doc/locales/.gitkeep +0 -0
- data/doc/man/rai.1.ronn +505 -0
- data/doc/operations/packaging.md +133 -0
- data/doc/operations/rosett-ai-release.md +65 -0
- data/doc/reference/error-catalog.md +107 -0
- data/doc/reference/rosett-ai-technical-reference.pdf +0 -0
- data/doc/reference/src/Pictures/cover.jpg +0 -0
- data/doc/reference/src/Pictures/head1.jpg +0 -0
- data/doc/reference/src/Pictures/head2.jpg +0 -0
- data/doc/reference/src/Pictures/head3.jpg +0 -0
- data/doc/reference/src/Pictures/head4.jpg +0 -0
- data/doc/reference/src/Pictures/head5.jpg +0 -0
- data/doc/reference/src/Pictures/head6.jpg +0 -0
- data/doc/reference/src/Pictures/head7.jpg +0 -0
- data/doc/reference/src/Pictures/head8.jpg +0 -0
- data/doc/reference/src/StyleInd.ist +4 -0
- data/doc/reference/src/bibliography.bib +79 -0
- data/doc/reference/src/main.tex +1288 -0
- data/doc/reference/src/structure.tex +303 -0
- data/doc/rosett-ai-bookmarks.html +301 -0
- data/kitchen.yml +46 -0
- data/lib/rosett_ai/adopter/executor_resolver.rb +77 -0
- data/lib/rosett_ai/adopter/local_analysis_collector.rb +154 -0
- data/lib/rosett_ai/adopter/rule_adopter.rb +254 -0
- data/lib/rosett_ai/ai_config/config_compiler.rb +111 -0
- data/lib/rosett_ai/ai_config/context_window.rb +55 -0
- data/lib/rosett_ai/ai_config/cost_controls.rb +44 -0
- data/lib/rosett_ai/ai_config/fallback_chain.rb +64 -0
- data/lib/rosett_ai/ai_config/model_router.rb +121 -0
- data/lib/rosett_ai/ai_config/validator.rb +45 -0
- data/lib/rosett_ai/authorship/attribution_compiler.rb +99 -0
- data/lib/rosett_ai/authorship/disclosure_policy.rb +81 -0
- data/lib/rosett_ai/authorship/review_validator.rb +39 -0
- data/lib/rosett_ai/authorship/trailer_generator.rb +88 -0
- data/lib/rosett_ai/backup/compressor.rb +180 -0
- data/lib/rosett_ai/backup/destination.rb +91 -0
- data/lib/rosett_ai/behaviour/manager.rb +156 -0
- data/lib/rosett_ai/compiler/backend.rb +86 -0
- data/lib/rosett_ai/compiler/backends/agents_md_backend.rb +80 -0
- data/lib/rosett_ai/compiler/backends/claude_backend.rb +88 -0
- data/lib/rosett_ai/compiler/backends/generic_backend.rb +15 -0
- data/lib/rosett_ai/compiler/behaviour_compiler.rb +40 -0
- data/lib/rosett_ai/compiler/capability_checker.rb +104 -0
- data/lib/rosett_ai/compiler/compilation_pipeline.rb +361 -0
- data/lib/rosett_ai/compiler/compiled_output.rb +39 -0
- data/lib/rosett_ai/compiler/locale_compiler.rb +250 -0
- data/lib/rosett_ai/compiler/target_profile.rb +112 -0
- data/lib/rosett_ai/completion/generator.rb +101 -0
- data/lib/rosett_ai/completion/shells/bash_generator.rb +126 -0
- data/lib/rosett_ai/completion/shells/fish_generator.rb +78 -0
- data/lib/rosett_ai/completion/shells/zsh_generator.rb +126 -0
- data/lib/rosett_ai/comply/checkers/cra_checker.rb +102 -0
- data/lib/rosett_ai/comply/checkers/license_checker.rb +85 -0
- data/lib/rosett_ai/comply/checkers/spdx_header_checker.rb +98 -0
- data/lib/rosett_ai/comply/reporter.rb +113 -0
- data/lib/rosett_ai/comply/runner.rb +50 -0
- data/lib/rosett_ai/composition/circular_dependency_detector.rb +56 -0
- data/lib/rosett_ai/composition/composer.rb +158 -0
- data/lib/rosett_ai/composition/composition_result.rb +64 -0
- data/lib/rosett_ai/composition/conflict_detector.rb +53 -0
- data/lib/rosett_ai/composition/lockfile.rb +103 -0
- data/lib/rosett_ai/composition/merge_strategy.rb +131 -0
- data/lib/rosett_ai/composition/priority_sorter.rb +29 -0
- data/lib/rosett_ai/composition/scope_resolver.rb +55 -0
- data/lib/rosett_ai/config/compile_result.rb +37 -0
- data/lib/rosett_ai/config/compiler.rb +13 -0
- data/lib/rosett_ai/config/domain_transformer.rb +13 -0
- data/lib/rosett_ai/config/key_map.rb +13 -0
- data/lib/rosett_ai/config/masking_secret_resolver.rb +40 -0
- data/lib/rosett_ai/config/scope_router.rb +13 -0
- data/lib/rosett_ai/config/secret_resolver.rb +125 -0
- data/lib/rosett_ai/configuration.rb +119 -0
- data/lib/rosett_ai/content/content_client.rb +60 -0
- data/lib/rosett_ai/content/pack_installer.rb +117 -0
- data/lib/rosett_ai/content/pack_manifest.rb +50 -0
- data/lib/rosett_ai/content/pack_registry.rb +68 -0
- data/lib/rosett_ai/content_packs/manager.rb +50 -0
- data/lib/rosett_ai/dbus/compositor_detector.rb +77 -0
- data/lib/rosett_ai/dbus/focus_adapters/base.rb +59 -0
- data/lib/rosett_ai/dbus/focus_adapters/gnome_adapter.rb +172 -0
- data/lib/rosett_ai/dbus/focus_adapters/hyprland_adapter.rb +77 -0
- data/lib/rosett_ai/dbus/focus_adapters/i3_adapter.rb +65 -0
- data/lib/rosett_ai/dbus/focus_adapters/kwin_adapter.rb +103 -0
- data/lib/rosett_ai/dbus/focus_adapters/x11_adapter.rb +105 -0
- data/lib/rosett_ai/dbus/focus_monitor_interface.rb +103 -0
- data/lib/rosett_ai/dbus/manager_interface.rb +213 -0
- data/lib/rosett_ai/dbus/plugin_manager_interface.rb +169 -0
- data/lib/rosett_ai/dbus/rate_limiter.rb +89 -0
- data/lib/rosett_ai/dbus/service.rb +121 -0
- data/lib/rosett_ai/dbus/status_notifier_interface.rb +79 -0
- data/lib/rosett_ai/deprecation.rb +79 -0
- data/lib/rosett_ai/desktop/dbus_client.rb +259 -0
- data/lib/rosett_ai/desktop/gtk4_app.rb +371 -0
- data/lib/rosett_ai/desktop/gtk4_preferences.rb +331 -0
- data/lib/rosett_ai/desktop/gui_logger.rb +236 -0
- data/lib/rosett_ai/doctor/check.rb +92 -0
- data/lib/rosett_ai/doctor/checks/cache_health_check.rb +50 -0
- data/lib/rosett_ai/doctor/checks/dbus_availability_check.rb +39 -0
- data/lib/rosett_ai/doctor/checks/engine_detection_check.rb +46 -0
- data/lib/rosett_ai/doctor/checks/file_permission_check.rb +44 -0
- data/lib/rosett_ai/doctor/checks/gem_dependency_check.rb +55 -0
- data/lib/rosett_ai/doctor/checks/ruby_version_check.rb +50 -0
- data/lib/rosett_ai/doctor/checks/stale_config_nncc_check.rb +57 -0
- data/lib/rosett_ai/doctor/checks/stale_home_nncc_check.rb +59 -0
- data/lib/rosett_ai/doctor.rb +81 -0
- data/lib/rosett_ai/documentation/reference_compiler.rb +122 -0
- data/lib/rosett_ai/documentation/translator.rb +62 -0
- data/lib/rosett_ai/engines/base_config_compiler.rb +203 -0
- data/lib/rosett_ai/engines/detector.rb +63 -0
- data/lib/rosett_ai/engines/registry.rb +50 -0
- data/lib/rosett_ai/error_handler.rb +139 -0
- data/lib/rosett_ai/exit_codes.rb +76 -0
- data/lib/rosett_ai/feature_flags.rb +102 -0
- data/lib/rosett_ai/formatting.rb +33 -0
- data/lib/rosett_ai/gem_consistency_checker.rb +199 -0
- data/lib/rosett_ai/git_hooks/chain_detector.rb +86 -0
- data/lib/rosett_ai/git_hooks/installer.rb +175 -0
- data/lib/rosett_ai/git_hooks/script_generator.rb +125 -0
- data/lib/rosett_ai/gitlab/validators/supplementary_gitlab_ci_yaml_validator.rb +79 -0
- data/lib/rosett_ai/i18n/locale_resolver.rb +46 -0
- data/lib/rosett_ai/i18n/utf8_checker.rb +32 -0
- data/lib/rosett_ai/init/config_file_writer.rb +24 -0
- data/lib/rosett_ai/init/directory_builder.rb +38 -0
- data/lib/rosett_ai/init/file_copier.rb +95 -0
- data/lib/rosett_ai/init/global_initializer.rb +28 -0
- data/lib/rosett_ai/init/local_initializer.rb +27 -0
- data/lib/rosett_ai/init/mcp_registrar.rb +109 -0
- data/lib/rosett_ai/init/project_initializer.rb +38 -0
- data/lib/rosett_ai/licensing/license_key.rb +139 -0
- data/lib/rosett_ai/licensing/license_store.rb +64 -0
- data/lib/rosett_ai/licensing/license_validator.rb +60 -0
- data/lib/rosett_ai/licensing/tier.rb +42 -0
- data/lib/rosett_ai/mcp/admin/auditor.rb +88 -0
- data/lib/rosett_ai/mcp/admin/health_checker.rb +81 -0
- data/lib/rosett_ai/mcp/admin/registry.rb +100 -0
- data/lib/rosett_ai/mcp/admin/schema_validator.rb +63 -0
- data/lib/rosett_ai/mcp/enforcement/.gitkeep +0 -0
- data/lib/rosett_ai/mcp/enforcement/hook_generator.rb +197 -0
- data/lib/rosett_ai/mcp/enforcement/validator.rb +215 -0
- data/lib/rosett_ai/mcp/governance.rb +160 -0
- data/lib/rosett_ai/mcp/http_security_config.rb +158 -0
- data/lib/rosett_ai/mcp/instructions.rb +266 -0
- data/lib/rosett_ai/mcp/key_hasher.rb +66 -0
- data/lib/rosett_ai/mcp/keyfile.rb +221 -0
- data/lib/rosett_ai/mcp/middleware/authentication.rb +146 -0
- data/lib/rosett_ai/mcp/middleware/content_type.rb +56 -0
- data/lib/rosett_ai/mcp/middleware/cors.rb +83 -0
- data/lib/rosett_ai/mcp/middleware/origin_validation.rb +73 -0
- data/lib/rosett_ai/mcp/middleware/rate_limit.rb +106 -0
- data/lib/rosett_ai/mcp/middleware/request_size.rb +51 -0
- data/lib/rosett_ai/mcp/plugins.rb +143 -0
- data/lib/rosett_ai/mcp/prompts/compilation_prompt.rb +40 -0
- data/lib/rosett_ai/mcp/prompts/compliance_prompt.rb +41 -0
- data/lib/rosett_ai/mcp/prompts/diagnostics_prompt.rb +41 -0
- data/lib/rosett_ai/mcp/prompts/validation_prompt.rb +41 -0
- data/lib/rosett_ai/mcp/resources/behaviour_resource.rb +127 -0
- data/lib/rosett_ai/mcp/resources/config_resource.rb +72 -0
- data/lib/rosett_ai/mcp/resources/design_resource.rb +58 -0
- data/lib/rosett_ai/mcp/resources/hooks_resource.rb +74 -0
- data/lib/rosett_ai/mcp/resources/provenance_resource.rb +51 -0
- data/lib/rosett_ai/mcp/resources/rules_resource.rb +60 -0
- data/lib/rosett_ai/mcp/resources/schema_resource.rb +72 -0
- data/lib/rosett_ai/mcp/response_helper.rb +46 -0
- data/lib/rosett_ai/mcp/security_logger.rb +60 -0
- data/lib/rosett_ai/mcp/server.rb +212 -0
- data/lib/rosett_ai/mcp/settings/server_installer.rb +112 -0
- data/lib/rosett_ai/mcp/settings/trust_manager.rb +142 -0
- data/lib/rosett_ai/mcp/tools/adopt_tool.rb +70 -0
- data/lib/rosett_ai/mcp/tools/backup_tool.rb +64 -0
- data/lib/rosett_ai/mcp/tools/behaviour_display_tool.rb +72 -0
- data/lib/rosett_ai/mcp/tools/behaviour_list_tool.rb +56 -0
- data/lib/rosett_ai/mcp/tools/behaviour_manage_tool.rb +114 -0
- data/lib/rosett_ai/mcp/tools/behaviour_show_tool.rb +62 -0
- data/lib/rosett_ai/mcp/tools/compile_status_tool.rb +122 -0
- data/lib/rosett_ai/mcp/tools/compile_tool.rb +191 -0
- data/lib/rosett_ai/mcp/tools/comply_tool.rb +79 -0
- data/lib/rosett_ai/mcp/tools/config_compile_tool.rb +71 -0
- data/lib/rosett_ai/mcp/tools/config_status_tool.rb +79 -0
- data/lib/rosett_ai/mcp/tools/content_tool.rb +78 -0
- data/lib/rosett_ai/mcp/tools/context_query_tool.rb +156 -0
- data/lib/rosett_ai/mcp/tools/design_list_tool.rb +57 -0
- data/lib/rosett_ai/mcp/tools/design_show_tool.rb +69 -0
- data/lib/rosett_ai/mcp/tools/doctor_tool.rb +62 -0
- data/lib/rosett_ai/mcp/tools/documentation_status_tool.rb +45 -0
- data/lib/rosett_ai/mcp/tools/engines_tool.rb +84 -0
- data/lib/rosett_ai/mcp/tools/hook_install_tool.rb +190 -0
- data/lib/rosett_ai/mcp/tools/hook_preview_tool.rb +173 -0
- data/lib/rosett_ai/mcp/tools/hooks_status_tool.rb +84 -0
- data/lib/rosett_ai/mcp/tools/init_tool.rb +87 -0
- data/lib/rosett_ai/mcp/tools/license_status_tool.rb +44 -0
- data/lib/rosett_ai/mcp/tools/project_tool.rb +117 -0
- data/lib/rosett_ai/mcp/tools/provenance_tool.rb +97 -0
- data/lib/rosett_ai/mcp/tools/provenance_write_tool.rb +40 -0
- data/lib/rosett_ai/mcp/tools/retrofit_tool.rb +81 -0
- data/lib/rosett_ai/mcp/tools/rule_search_tool.rb +163 -0
- data/lib/rosett_ai/mcp/tools/schema_get_tool.rb +94 -0
- data/lib/rosett_ai/mcp/tools/tooling_tool.rb +86 -0
- data/lib/rosett_ai/mcp/tools/validate_tool.rb +105 -0
- data/lib/rosett_ai/mcp/tools/workflow_execute_tool.rb +74 -0
- data/lib/rosett_ai/mcp/tools/workflow_tool.rb +78 -0
- data/lib/rosett_ai/migration/detector.rb +117 -0
- data/lib/rosett_ai/migration/nncc_config_migrator.rb +94 -0
- data/lib/rosett_ai/migration/nncc_project_migrator.rb +90 -0
- data/lib/rosett_ai/migration/xdg_migrator.rb +123 -0
- data/lib/rosett_ai/package_manager/apt.rb +108 -0
- data/lib/rosett_ai/package_manager/base.rb +68 -0
- data/lib/rosett_ai/package_manager/gem_backend.rb +90 -0
- data/lib/rosett_ai/packaging/variant_config.rb +92 -0
- data/lib/rosett_ai/path_resolver.rb +115 -0
- data/lib/rosett_ai/plugins/contract.rb +43 -0
- data/lib/rosett_ai/plugins/engine_contract.rb +60 -0
- data/lib/rosett_ai/plugins/gui_contract.rb +74 -0
- data/lib/rosett_ai/plugins/mcp_contract.rb +48 -0
- data/lib/rosett_ai/plugins/registry.rb +150 -0
- data/lib/rosett_ai/policy/auditor.rb +41 -0
- data/lib/rosett_ai/policy/deny_list.rb +71 -0
- data/lib/rosett_ai/policy/opt_out_scanner.rb +37 -0
- data/lib/rosett_ai/policy/policy_compiler.rb +84 -0
- data/lib/rosett_ai/policy/protected_files.rb +47 -0
- data/lib/rosett_ai/policy/tier_hierarchy.rb +48 -0
- data/lib/rosett_ai/policy/validator.rb +35 -0
- data/lib/rosett_ai/profiler.rb +79 -0
- data/lib/rosett_ai/project/drift_detector.rb +126 -0
- data/lib/rosett_ai/project/manager.rb +115 -0
- data/lib/rosett_ai/project/sync_manager.rb +138 -0
- data/lib/rosett_ai/project/template_applier.rb +105 -0
- data/lib/rosett_ai/project_context.rb +82 -0
- data/lib/rosett_ai/provenance/entry.rb +63 -0
- data/lib/rosett_ai/provenance/file_source.rb +32 -0
- data/lib/rosett_ai/provenance/source.rb +62 -0
- data/lib/rosett_ai/provenance/store.rb +153 -0
- data/lib/rosett_ai/provenance/tracker.rb +62 -0
- data/lib/rosett_ai/provenance/trailer_generator.rb +43 -0
- data/lib/rosett_ai/provenance/validator.rb +45 -0
- data/lib/rosett_ai/quorum/collector.rb +59 -0
- data/lib/rosett_ai/quorum/comparator.rb +81 -0
- data/lib/rosett_ai/quorum/dispatcher.rb +57 -0
- data/lib/rosett_ai/quorum/strategies/adopt.rb +56 -0
- data/lib/rosett_ai/rai_config.rb +107 -0
- data/lib/rosett_ai/retrofit/base_parser.rb +66 -0
- data/lib/rosett_ai/retrofit/engine.rb +171 -0
- data/lib/rosett_ai/retrofit/parsers/agents_md_parser.rb +50 -0
- data/lib/rosett_ai/retrofit/parsers/claude_parser.rb +69 -0
- data/lib/rosett_ai/retrofit/parsers/cursor_parser.rb +82 -0
- data/lib/rosett_ai/retrofit/round_trip_validator.rb +65 -0
- data/lib/rosett_ai/retrofit/scanner.rb +47 -0
- data/lib/rosett_ai/retrofit/secret_detector.rb +87 -0
- data/lib/rosett_ai/secrets_resolver.rb +71 -0
- data/lib/rosett_ai/smart_feedback/suggester.rb +83 -0
- data/lib/rosett_ai/smart_feedback/thor_middleware.rb +84 -0
- data/lib/rosett_ai/structured_logger.rb +110 -0
- data/lib/rosett_ai/telemetry/json_lines_writer.rb +50 -0
- data/lib/rosett_ai/telemetry/log_rotator.rb +67 -0
- data/lib/rosett_ai/telemetry/provider.rb +26 -0
- data/lib/rosett_ai/telemetry/reporter.rb +144 -0
- data/lib/rosett_ai/telemetry.rb +47 -0
- data/lib/rosett_ai/text_sanitizer.rb +62 -0
- data/lib/rosett_ai/thor/cli.rb +269 -0
- data/lib/rosett_ai/thor/tasks/adopt.rb +250 -0
- data/lib/rosett_ai/thor/tasks/backup.rb +420 -0
- data/lib/rosett_ai/thor/tasks/behaviour.rb +474 -0
- data/lib/rosett_ai/thor/tasks/build.rb +1162 -0
- data/lib/rosett_ai/thor/tasks/compile.rb +415 -0
- data/lib/rosett_ai/thor/tasks/completion.rb +123 -0
- data/lib/rosett_ai/thor/tasks/comply.rb +82 -0
- data/lib/rosett_ai/thor/tasks/config.rb +265 -0
- data/lib/rosett_ai/thor/tasks/content.rb +193 -0
- data/lib/rosett_ai/thor/tasks/dbus.rb +321 -0
- data/lib/rosett_ai/thor/tasks/design.rb +258 -0
- data/lib/rosett_ai/thor/tasks/desktop.rb +129 -0
- data/lib/rosett_ai/thor/tasks/doctor.rb +127 -0
- data/lib/rosett_ai/thor/tasks/documentation.rb +321 -0
- data/lib/rosett_ai/thor/tasks/engines.rb +167 -0
- data/lib/rosett_ai/thor/tasks/hooks.rb +219 -0
- data/lib/rosett_ai/thor/tasks/init.rb +259 -0
- data/lib/rosett_ai/thor/tasks/license.rb +120 -0
- data/lib/rosett_ai/thor/tasks/mcp.rb +535 -0
- data/lib/rosett_ai/thor/tasks/migrate.rb +121 -0
- data/lib/rosett_ai/thor/tasks/plugins.rb +157 -0
- data/lib/rosett_ai/thor/tasks/project.rb +260 -0
- data/lib/rosett_ai/thor/tasks/provenance.rb +195 -0
- data/lib/rosett_ai/thor/tasks/release.rb +314 -0
- data/lib/rosett_ai/thor/tasks/retrofit.rb +90 -0
- data/lib/rosett_ai/thor/tasks/tooling.rb +308 -0
- data/lib/rosett_ai/thor/tasks/validate.rb +108 -0
- data/lib/rosett_ai/thor/tasks/workflow.rb +196 -0
- data/lib/rosett_ai/tooling/ci_yaml_validator.rb +37 -0
- data/lib/rosett_ai/tooling/version_checker.rb +35 -0
- data/lib/rosett_ai/ui/accessible_tui.rb +61 -0
- data/lib/rosett_ai/ui/base.rb +46 -0
- data/lib/rosett_ai/ui/gtk4.rb +98 -0
- data/lib/rosett_ai/ui/kde.rb +40 -0
- data/lib/rosett_ai/ui/qt6.rb +40 -0
- data/lib/rosett_ai/ui/registry.rb +60 -0
- data/lib/rosett_ai/ui/tty_helper.rb +74 -0
- data/lib/rosett_ai/ui/tui.rb +59 -0
- data/lib/rosett_ai/validators/behaviour_validator.rb +20 -0
- data/lib/rosett_ai/validators/design_validator.rb +17 -0
- data/lib/rosett_ai/validators/schema_validator.rb +84 -0
- data/lib/rosett_ai/validators/tooling_validator.rb +17 -0
- data/lib/rosett_ai/version.rb +8 -0
- data/lib/rosett_ai/version_consistency_checker.rb +129 -0
- data/lib/rosett_ai/workflow/audit_log.rb +86 -0
- data/lib/rosett_ai/workflow/engine.rb +142 -0
- data/lib/rosett_ai/workflow/manager.rb +82 -0
- data/lib/rosett_ai/workflow/schema_validator.rb +71 -0
- data/lib/rosett_ai/workflow/step_runner.rb +61 -0
- data/lib/rosett_ai/workflow/steps/prompt_step.rb +62 -0
- data/lib/rosett_ai/workflow/steps/rai_step.rb +74 -0
- data/lib/rosett_ai/workflow/steps/shell_step.rb +53 -0
- data/lib/rosett_ai/yaml_loader.rb +78 -0
- data/lib/rosett_ai.rb +221 -0
- data/lib/rubocop/cop/rosett_ai/shell_interpolation.rb +54 -0
- data/lib/rubocop/cop/rosett_ai/unsafe_const_get.rb +60 -0
- data/lib/rubocop/cop/rosett_ai/unsafe_send.rb +50 -0
- data/lib/rubocop/cop/rosett_ai/unsafe_yaml_load.rb +40 -0
- data/lib/rubocop/rosett_ai.rb +9 -0
- data/lib/scripts/generated/docker_hub_tags.rb +126 -0
- data/locales/.gitkeep +0 -0
- data/locales/ar.yml +579 -0
- data/locales/en.yml +571 -0
- data/locales/fr.yml +567 -0
- data/packaging/build-engine-deb.sh +81 -0
- data/packaging/scripts/postinst +17 -0
- data/packaging/scripts/postrm +19 -0
- data/packaging/scripts/prerm +10 -0
- data/packaging/wrapper.sh.template +38 -0
- data/rosett-ai.gemspec +63 -0
- data/rules/.gitkeep +0 -0
- data/scripts/publish/pulp_upload.sh +123 -0
- data/settings.json +29 -0
- data/share/applications/be.neatnerds.rosettai.desktop +29 -0
- data/share/dbus-1/interfaces/be.neatnerds.rosettai.xml +103 -0
- data/share/dbus-1/services/be.neatnerds.rosettai.service +3 -0
- data/share/templates/behaviour/criticalthinking.yml +69 -0
- metadata +810 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Replace Omnibus with fpm + ruby-build for Debian packaging
|
|
2
|
+
|
|
3
|
+
**Branch**: `packaging_fpm`
|
|
4
|
+
**Date**: 2026-02-02 through 2026-02-18
|
|
5
|
+
**Commits**: 11 (excluding merge commit)
|
|
6
|
+
|
|
7
|
+
## Motivation
|
|
8
|
+
|
|
9
|
+
The Omnibus-based packaging approach had become a significant maintenance burden. Key issues included:
|
|
10
|
+
|
|
11
|
+
- **Excessive complexity**: 11 configuration files across `omnibus/` (Gemfile, Rakefile, omnibus.rb, project config, software config, ERB template, 4 package scripts)
|
|
12
|
+
- **Long build times**: 30-60 minutes per build due to compiling Ruby, OpenSSL, zlib, and libyaml from source
|
|
13
|
+
- **Large package size**: 100MB+ `.deb` files
|
|
14
|
+
- **Hardcoded gem paths**: The wrapper template used hardcoded `gems/3.3.0` paths that would break on Ruby version changes
|
|
15
|
+
- **Missing conffiles**: dpkg did not track `/etc/rosett-ai/settings.json`, causing user edits to be overwritten on upgrade
|
|
16
|
+
- **Broken symlinks**: Removal scripts did not properly clean up `/usr/local/bin/raictl`
|
|
17
|
+
- **Dependency overrides**: OpenSSL, zlib, libyaml were compiled from source despite always being present on Debian systems
|
|
18
|
+
|
|
19
|
+
## Solution: fpm + ruby-build
|
|
20
|
+
|
|
21
|
+
The replacement uses two focused tools:
|
|
22
|
+
|
|
23
|
+
- **ruby-build** (already mirrored internally via rbenv): Compiles Ruby to `/opt/rosett-ai/embedded/` with system-linked shared libraries
|
|
24
|
+
- **fpm** (~> 1.16, Ruby gem): Packages the staging directory into a `.deb` with proper metadata
|
|
25
|
+
|
|
26
|
+
### Key design decisions
|
|
27
|
+
|
|
28
|
+
1. **No source compilation of system libraries** - libc6, libssl3, zlib1g, and libyaml-0-2 are declared as package dependencies instead of being compiled from source. These are always present on Debian systems.
|
|
29
|
+
2. **Dynamic GEM_HOME** - The wrapper script uses `RbConfig::CONFIG["ruby_program_version"]` instead of hardcoded paths.
|
|
30
|
+
3. **Direct Ruby invocation** - `exec ruby -I"${APP_DIR}/lib" bin/raictl "$@"` instead of `bundle exec`, eliminating bundler overhead at runtime.
|
|
31
|
+
4. **dpkg conffile management** - `settings.json` ships at `/etc/rosett-ai/settings.json` and is registered as a conffile, so dpkg handles user modifications on upgrade.
|
|
32
|
+
|
|
33
|
+
## Changes by area
|
|
34
|
+
|
|
35
|
+
### New: Packaging infrastructure
|
|
36
|
+
|
|
37
|
+
| File | Purpose |
|
|
38
|
+
|------|---------|
|
|
39
|
+
| `lib/rosett_ai/thor/tasks/build.rb` (650 LOC) | Thor task orchestrating the full build pipeline |
|
|
40
|
+
| `packaging/wrapper.sh` | Runtime wrapper with dynamic paths |
|
|
41
|
+
| `packaging/scripts/postinst` | Post-install: creates `/usr/local/bin/raictl` symlink |
|
|
42
|
+
| `packaging/scripts/prerm` | Pre-removal: removes symlink |
|
|
43
|
+
| `packaging/scripts/postrm` | Post-removal: purge removes `/opt/rosett-ai` and `/etc/rosett-ai` |
|
|
44
|
+
|
|
45
|
+
The build task implements a stage-based pipeline:
|
|
46
|
+
|
|
47
|
+
1. **Validate environment** - Checks for ruby-build, fakeroot, dpkg-deb
|
|
48
|
+
2. **Compile Ruby** - Runs ruby-build into staging directory
|
|
49
|
+
3. **Sync application** - Copies app files (excluding `.git`, `spec`, `tmp`, `pkg`, etc.)
|
|
50
|
+
4. **Install gems** - `bundle install --deployment` using embedded Ruby
|
|
51
|
+
5. **Install wrapper** - Copies wrapper script to `staging/opt/rosett-ai/bin/raictl`
|
|
52
|
+
6. **Install default config** - Ships settings.json as dpkg conffile
|
|
53
|
+
7. **Build package** - Invokes fpm with all metadata, dependencies, and maintainer scripts
|
|
54
|
+
|
|
55
|
+
Each stage is timed and reported in a summary table. Failures are caught and reported with context. Build logs are written to `tmp/build-<uuid>.log`.
|
|
56
|
+
|
|
57
|
+
#### Build context extraction
|
|
58
|
+
|
|
59
|
+
Mutable build state (build ID, log path, timings, failure info) was extracted into a `BuildContext` Struct to reduce class-level instance variable count and improve testability:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
BuildContext = Struct.new(
|
|
63
|
+
:build_id, :build_start, :build_log,
|
|
64
|
+
:stage_timings, :completed_stages,
|
|
65
|
+
:failed_stage, :failure_reason, :summary_printed,
|
|
66
|
+
keyword_init: true
|
|
67
|
+
)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Removed: Omnibus
|
|
71
|
+
|
|
72
|
+
All files under `omnibus/` were deleted:
|
|
73
|
+
|
|
74
|
+
- `omnibus/Gemfile` and `omnibus/Rakefile`
|
|
75
|
+
- `omnibus/omnibus.rb` (configuration)
|
|
76
|
+
- `omnibus/config/projects/rosett-ai.rb` (project definition)
|
|
77
|
+
- `omnibus/config/software/rosett-ai.rb` (software definition)
|
|
78
|
+
- `omnibus/config/templates/rosett-ai/nncc_wrapper.erb` (ERB wrapper template)
|
|
79
|
+
- `omnibus/package-scripts/rosett-ai/{postinst,postrm,preinst,prerm}` (4 scripts)
|
|
80
|
+
|
|
81
|
+
### Refactored: Init task
|
|
82
|
+
|
|
83
|
+
The `init` command (`lib/rosett_ai/thor/tasks/init.rb`) was rewritten:
|
|
84
|
+
|
|
85
|
+
- Extracted `RosettAi::Init::DirectoryBuilder` and `RosettAi::Init::FileCopier` for clear separation
|
|
86
|
+
- `setup_global` and `setup_local` now properly delegate and store results
|
|
87
|
+
- Added `--no-compile` flag to skip the compilation step after init
|
|
88
|
+
- Summary table shows directory and file counts per scope
|
|
89
|
+
|
|
90
|
+
### Refactored: Tooling
|
|
91
|
+
|
|
92
|
+
- `lib/rosett_ai/thor/tasks/tooling.rb` was simplified from a multi-tool manager (~420 LOC) to a focused GitLab CI YAML validator
|
|
93
|
+
- Five orphaned library files removed from `lib/rosett_ai/tooling/`:
|
|
94
|
+
- `detector.rb`, `display_helpers.rb`, `package_manager.rb`, `settings_manager.rb`, `tool_registry.rb`
|
|
95
|
+
- Four corresponding spec files removed from `spec/rosett_ai/tooling/`
|
|
96
|
+
|
|
97
|
+
### CI/CD pipeline
|
|
98
|
+
|
|
99
|
+
| File | Change |
|
|
100
|
+
|------|--------|
|
|
101
|
+
| `.gitlab-ci-files/build/omnibus.yml` | Renamed to `package.yml`, rewritten for fpm |
|
|
102
|
+
| `.gitlab-ci-files/global/defaults.yml` | Added retry policy (max 2) for transient failures |
|
|
103
|
+
| `.gitlab-ci-files/global/stages.yml` | Updated stage ordering |
|
|
104
|
+
| `.gitlab-ci-files/global/variables.yml` | Added build-related variables |
|
|
105
|
+
| `.gitlab-ci-files/security_scan/gitleaks.yml` | Fixed false positives |
|
|
106
|
+
| `.gitlab-ci.yml` | Updated include path from omnibus to package |
|
|
107
|
+
| `.gitleaks.toml` | Added allowlist entries for test fixtures |
|
|
108
|
+
|
|
109
|
+
Retry policy covers: `runner_system_failure`, `job_execution_timeout`, `stuck_or_timeout_failure`, `api_failure`.
|
|
110
|
+
|
|
111
|
+
### Test suite
|
|
112
|
+
|
|
113
|
+
- **277 examples, 0 failures** (up from ~230 on main)
|
|
114
|
+
- **92.92% line coverage**
|
|
115
|
+
- New spec files:
|
|
116
|
+
- `spec/rosett_ai/thor/tasks/build_package_spec.rb` (30 examples)
|
|
117
|
+
- `spec/rosett_ai/thor/tasks/build_spec.rb` (2 examples)
|
|
118
|
+
- `spec/rosett_ai/thor/tasks/init_spec.rb` (rewritten, 9 examples)
|
|
119
|
+
|
|
120
|
+
#### Thor warning suppression
|
|
121
|
+
|
|
122
|
+
RSpec's `allow_any_instance_of` uses `define_method` on Thor subclasses, which triggers Thor's `method_added` hook and produces `[WARNING] Attempted to create command` messages. This was solved by:
|
|
123
|
+
|
|
124
|
+
1. Prepending `ThorTestSilencer` on `Thor.singleton_class` in `spec_helper.rb` - temporarily redirects `$stdout` during `create_command` to suppress the warning output
|
|
125
|
+
2. Using `allow_any_instance_of` (required because Thor's `invoke` creates a new internal instance, making instance-level stubs ineffective)
|
|
126
|
+
3. Adding `RSpec/AnyInstance` exclusion for `spec/rosett_ai/thor/tasks/**/*` in `.rubocop.yml`
|
|
127
|
+
|
|
128
|
+
### Code quality configuration
|
|
129
|
+
|
|
130
|
+
- `.reek.yml`: Added targeted exclusions for the build task's inherent complexity (stage orchestration, environment validation, summary printing)
|
|
131
|
+
- `.rubocop.yml`: Added `RSpec/AnyInstance` exclusion for Thor task specs
|
|
132
|
+
- All linters pass clean: rubocop (0 offenses), reek (0 warnings), overcommit (all hooks OK)
|
|
133
|
+
|
|
134
|
+
## Package details
|
|
135
|
+
|
|
136
|
+
| Property | Value |
|
|
137
|
+
|----------|-------|
|
|
138
|
+
| Package name | `rosett-ai` |
|
|
139
|
+
| Install path | `/opt/rosett-ai/` |
|
|
140
|
+
| Config path | `/etc/rosett-ai/` |
|
|
141
|
+
| Binary symlink | `/usr/local/bin/raictl` |
|
|
142
|
+
| Embedded Ruby | `/opt/rosett-ai/embedded/bin/ruby` |
|
|
143
|
+
| Package size | ~38 MB |
|
|
144
|
+
| Build time | ~5 minutes |
|
|
145
|
+
| Dependencies | libc6, libssl3, zlib1g, libyaml-0-2 |
|
|
146
|
+
| Compression | xz |
|
|
147
|
+
|
|
148
|
+
## Verification checklist
|
|
149
|
+
|
|
150
|
+
- [x] `bin/raictl build package --clean --verbose` produces a `.deb`
|
|
151
|
+
- [x] `dpkg-deb --info` shows correct metadata and dependencies
|
|
152
|
+
- [x] `bundle exec rspec` passes (277 examples, 0 failures)
|
|
153
|
+
- [x] `bundle exec rubocop` passes (0 offenses)
|
|
154
|
+
- [x] `bundle exec reek` passes (0 warnings)
|
|
155
|
+
- [x] `overcommit -r` passes (all hooks OK)
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Implement testing.yml design document (P1)
|
|
2
|
+
|
|
3
|
+
**Branch**: `design_implementation`
|
|
4
|
+
**Date**: 2026-02-19
|
|
5
|
+
**Design doc**: `conf/design/testing.yml` v1.0.0
|
|
6
|
+
|
|
7
|
+
## Motivation
|
|
8
|
+
|
|
9
|
+
The testing design document establishes a testing strategy that validates both
|
|
10
|
+
code correctness AND test quality. When AI writes both code and tests, the test
|
|
11
|
+
suite can be perfectly consistent yet meaningless. Mutation testing (Mutant) acts
|
|
12
|
+
as an independent third-party validator that mechanically verifies tests catch
|
|
13
|
+
real bugs, regardless of who wrote them.
|
|
14
|
+
|
|
15
|
+
This must be in place before feature development begins so all future code is
|
|
16
|
+
validated from day one. Testing is the second P1 domain after security.
|
|
17
|
+
|
|
18
|
+
## Acceptance criteria
|
|
19
|
+
|
|
20
|
+
All 9 acceptance criteria from `testing.yml` are satisfied:
|
|
21
|
+
|
|
22
|
+
| # | Criterion | Evidence |
|
|
23
|
+
|---|-----------|----------|
|
|
24
|
+
| 1 | mutant-rspec installed + `.mutant.yml` | `.mutant.yml` with `usage: opensource`, rspec integration |
|
|
25
|
+
| 2 | SimpleCov thresholds block CI | 90% overall, 80% per-file (was 50/40) |
|
|
26
|
+
| 3 | RSpec on every CI push + JUnit XML | Already existed in `.gitlab-ci-files/test/rspec.yml` |
|
|
27
|
+
| 4 | Mutant on MR, blocks merge | `.gitlab-ci-files/test/mutant.yml`, `allow_failure: false` |
|
|
28
|
+
| 5 | factory_bot factories | `spec/support/factories/{behaviours,rules}.rb` |
|
|
29
|
+
| 6 | shared_examples for UI base | `spec/support/shared_examples/ui_implementation.rb` |
|
|
30
|
+
| 7 | Test fixtures | 7 files in `spec/fixtures/behaviours/` |
|
|
31
|
+
| 8 | AI test review checklist | `doc/ai_test_review_checklist.md` (11 points) |
|
|
32
|
+
| 9 | Property-based test | `spec/rosett_ai/yaml_loader_property_spec.rb` using Rantly |
|
|
33
|
+
|
|
34
|
+
## Changes by area
|
|
35
|
+
|
|
36
|
+
### New gems
|
|
37
|
+
|
|
38
|
+
| Gem | Version | Purpose |
|
|
39
|
+
|-----|---------|---------|
|
|
40
|
+
| `factory_bot` | ~> 6.5 | Test data factories for domain objects |
|
|
41
|
+
| `mutant-rspec` | ~> 0.14 | Mutation testing with RSpec integration |
|
|
42
|
+
| `rantly` | ~> 3.0 | Property-based / generative testing |
|
|
43
|
+
|
|
44
|
+
### Mutant configuration (`.mutant.yml`)
|
|
45
|
+
|
|
46
|
+
```yaml
|
|
47
|
+
usage: opensource # No license gem needed (>= 0.12)
|
|
48
|
+
integration: rspec
|
|
49
|
+
mutation:
|
|
50
|
+
timeout: 10.0
|
|
51
|
+
matcher:
|
|
52
|
+
subjects: # Library code with logic
|
|
53
|
+
- "RosettAi::Compiler*"
|
|
54
|
+
- "RosettAi::YamlLoader"
|
|
55
|
+
- "RosettAi::TextSanitizer"
|
|
56
|
+
- "RosettAi::Validators*"
|
|
57
|
+
- "RosettAi::Configuration"
|
|
58
|
+
- "RosettAi::Adopter*"
|
|
59
|
+
ignore: # CLI wiring (integration-tested)
|
|
60
|
+
- "RosettAi::Thor*"
|
|
61
|
+
- "RosettAi::VERSION"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Verified mutation scores (after targeted test hardening):
|
|
65
|
+
|
|
66
|
+
- **Overall**: 97.83% (555 mutations, 543 killed, 12 equivalent survivors)
|
|
67
|
+
- **YamlLoader**: all non-equivalent mutations killed
|
|
68
|
+
- **TextSanitizer**: all non-equivalent mutations killed
|
|
69
|
+
- **Configuration**: all non-equivalent mutations killed
|
|
70
|
+
|
|
71
|
+
The 12 surviving mutations are all provably equivalent:
|
|
72
|
+
|
|
73
|
+
| Pattern | Count | Why equivalent |
|
|
74
|
+
|---------|-------|---------------|
|
|
75
|
+
| `RosettAi::ValidationError` → `ValidationError` | 3 | Same class resolution inside `module RosettAi` |
|
|
76
|
+
| `is_a?` → `instance_of?` | 4 | JSON.parse only produces plain Hash/Array |
|
|
77
|
+
| `to_h.empty?` → `.empty?` / `to_hash.empty?` | 2 | Same behaviour for Hash |
|
|
78
|
+
| `e.message` → `e` | 1 | StandardError#to_s == #message |
|
|
79
|
+
| `.each_value.sum` → `.sum` | 1 | Hash#sum yields [k,v]; count_keys(string)=0 |
|
|
80
|
+
| `.unicode_normalize(:nfc)` → `.unicode_normalize` | 1 | Ruby defaults to :nfc |
|
|
81
|
+
|
|
82
|
+
### CI pipeline
|
|
83
|
+
|
|
84
|
+
New job in `.gitlab-ci-files/test/mutant.yml`:
|
|
85
|
+
|
|
86
|
+
- Runs only on merge requests (`merge_request_event`)
|
|
87
|
+
- Uses `--since $CI_MERGE_REQUEST_TARGET_BRANCH_NAME` for incremental analysis
|
|
88
|
+
- `GIT_DEPTH: 0` overrides the global shallow clone (mutant needs full history)
|
|
89
|
+
- `allow_failure: false` blocks merge if mutation score drops
|
|
90
|
+
|
|
91
|
+
### SimpleCov changes
|
|
92
|
+
|
|
93
|
+
| Setting | Before | After |
|
|
94
|
+
|---------|--------|-------|
|
|
95
|
+
| `minimum_coverage` | 50% | 90% |
|
|
96
|
+
| `minimum_coverage_by_file` | 40% | 80% |
|
|
97
|
+
| Coverage groups | Library, Thor Tasks, Validators | Compilers, Validators, Adopter, Backup, Thor Tasks |
|
|
98
|
+
|
|
99
|
+
Current coverage: **93.09%** (1562/1678 lines), well above the 90% threshold.
|
|
100
|
+
|
|
101
|
+
### factory_bot factories
|
|
102
|
+
|
|
103
|
+
Hash-based factories (not ActiveRecord) using `initialize_with` and `skip_create`:
|
|
104
|
+
|
|
105
|
+
- **`:behaviour`** — full behaviour hash with traits `:sensitive`, `:empty_rules`
|
|
106
|
+
- **`:rule`** — rule hash with sequenced IDs, traits `:disabled`, `:high_priority`
|
|
107
|
+
|
|
108
|
+
All keys are strings (matching YAML load output).
|
|
109
|
+
|
|
110
|
+
### Test fixtures
|
|
111
|
+
|
|
112
|
+
| Fixture | Tests |
|
|
113
|
+
|---------|-------|
|
|
114
|
+
| `valid_behaviour.yml` | Happy path with 2 rules |
|
|
115
|
+
| `invalid_missing_fields.yml` | Missing `name` and `version` |
|
|
116
|
+
| `invalid_bad_types.yml` | Priority as string, enabled as string |
|
|
117
|
+
| `malicious_yaml_bomb.yml` | YAML anchor expansion (blocked by `safe_load`) |
|
|
118
|
+
| `malicious_ansi_injection.yml` | ANSI CSI + OSC sequences in descriptions |
|
|
119
|
+
| `unicode_nfc.yml` | Precomposed NFC diacritics |
|
|
120
|
+
| `unicode_mixed.yml` | NFD decomposed, CJK, RTL Arabic |
|
|
121
|
+
|
|
122
|
+
### Property-based test
|
|
123
|
+
|
|
124
|
+
`spec/rosett_ai/yaml_loader_property_spec.rb` uses Rantly to verify:
|
|
125
|
+
|
|
126
|
+
1. Random valid hashes within bounds always load successfully (50 trials)
|
|
127
|
+
2. Random oversized payloads always raise `ValidationError` (10 trials)
|
|
128
|
+
3. Random deeply-nested structures beyond depth limit always raise (10 trials)
|
|
129
|
+
|
|
130
|
+
### Shared examples
|
|
131
|
+
|
|
132
|
+
`spec/support/shared_examples/ui_implementation.rb` defines the interface
|
|
133
|
+
contract (`:render`, `:display_table`, `:prompt_user`, `:show_spinner`) that
|
|
134
|
+
future UI implementations must satisfy. No consumers yet (P3 — ui_framework).
|
|
135
|
+
|
|
136
|
+
### AI test review checklist
|
|
137
|
+
|
|
138
|
+
`doc/ai_test_review_checklist.md` documents the 11-point checklist:
|
|
139
|
+
|
|
140
|
+
1. No tautological assertions
|
|
141
|
+
2. Concrete expected values
|
|
142
|
+
3. Mocks only at boundaries
|
|
143
|
+
4. Unit under test never mocked
|
|
144
|
+
5. Both positive and negative assertions
|
|
145
|
+
6. Edge cases: empty, nil, boundary values
|
|
146
|
+
7. Error cases tested
|
|
147
|
+
8. No implementation coupling
|
|
148
|
+
9. Behaviour-focused descriptions
|
|
149
|
+
10. Mutant score >= 85%
|
|
150
|
+
11. No redundant tests
|
|
151
|
+
|
|
152
|
+
### Test hardening (mutation coverage 80% → 97.83%)
|
|
153
|
+
|
|
154
|
+
Targeted test improvements to kill 99 of the 111 surviving mutations:
|
|
155
|
+
|
|
156
|
+
**`spec/rosett_ai/configuration_spec.rb`**:
|
|
157
|
+
|
|
158
|
+
- Replaced `include` matchers with exact `eq` assertions
|
|
159
|
+
- Added nested hash deep-merge, array union, and deduplication tests
|
|
160
|
+
- Added type-mismatch tests: scalar↔hash, scalar↔array in both directions
|
|
161
|
+
- Added object identity test for empty overlay (kills `deep_merge` guard mutations)
|
|
162
|
+
- Added error message content assertion (parser detail, not just filename)
|
|
163
|
+
- Full `reload!` coverage: all 4 ivars cleared, re-reads files after reload
|
|
164
|
+
- Exact `schema_path` assertion with `eq` instead of `end_with`
|
|
165
|
+
|
|
166
|
+
**`spec/rosett_ai/yaml_loader_spec.rb`**:
|
|
167
|
+
|
|
168
|
+
- Restructured all tests under `describe '.load_file'` (mutant test selection fix)
|
|
169
|
+
- Added `Time` class permitted-by-default test
|
|
170
|
+
- Added boundary tests at exactly 11 levels (hash and array nesting)
|
|
171
|
+
- Added exact byte count and key count in error message assertions
|
|
172
|
+
- Added mixed structure key counting: arrays-of-hashes, nested totals
|
|
173
|
+
- Fixed context wording for RuboCop `RSpec/ContextWording`
|
|
174
|
+
|
|
175
|
+
**`spec/rosett_ai/text_sanitizer_spec.rb`**:
|
|
176
|
+
|
|
177
|
+
- Added non-UTF-8 encoding test (ISO-8859-1 → UTF-8 conversion)
|
|
178
|
+
- Added NFC-specific single codepoint assertion (length=1, not NFD length=2)
|
|
179
|
+
- Added encoding verification on output
|
|
180
|
+
|
|
181
|
+
## Files created
|
|
182
|
+
|
|
183
|
+
| File | Purpose |
|
|
184
|
+
|------|---------|
|
|
185
|
+
| `.mutant.yml` | Mutant configuration |
|
|
186
|
+
| `.gitlab-ci-files/test/mutant.yml` | Mutant CI job (MR only) |
|
|
187
|
+
| `spec/support/factory_bot.rb` | FactoryBot require + RSpec config |
|
|
188
|
+
| `spec/support/factories/behaviours.rb` | Behaviour hash factory |
|
|
189
|
+
| `spec/support/factories/rules.rb` | Rule hash factory |
|
|
190
|
+
| `spec/support/shared_examples/ui_implementation.rb` | UI interface contract |
|
|
191
|
+
| `spec/fixtures/behaviours/valid_behaviour.yml` | Valid fixture |
|
|
192
|
+
| `spec/fixtures/behaviours/invalid_missing_fields.yml` | Invalid fixture |
|
|
193
|
+
| `spec/fixtures/behaviours/invalid_bad_types.yml` | Invalid fixture |
|
|
194
|
+
| `spec/fixtures/behaviours/malicious_yaml_bomb.yml` | Malicious fixture |
|
|
195
|
+
| `spec/fixtures/behaviours/malicious_ansi_injection.yml` | Malicious fixture |
|
|
196
|
+
| `spec/fixtures/behaviours/unicode_nfc.yml` | Unicode fixture |
|
|
197
|
+
| `spec/fixtures/behaviours/unicode_mixed.yml` | Unicode fixture |
|
|
198
|
+
| `doc/ai_test_review_checklist.md` | 11-point review checklist |
|
|
199
|
+
| `spec/rosett_ai/yaml_loader_property_spec.rb` | Property-based test |
|
|
200
|
+
| `doc/changes/2026-02-19-testing-infrastructure.md` | This change doc |
|
|
201
|
+
|
|
202
|
+
## Files modified
|
|
203
|
+
|
|
204
|
+
| File | Change |
|
|
205
|
+
|------|--------|
|
|
206
|
+
| `Gemfile` | Added factory_bot, mutant-rspec, rantly |
|
|
207
|
+
| `spec/spec_helper.rb` | Thresholds 90/80, coverage groups, support autoloading |
|
|
208
|
+
| `.gitlab-ci.yml` | Added mutant.yml include |
|
|
209
|
+
| `.rubocop.yml` | Added rubocop-factory_bot plugin |
|
|
210
|
+
| `CHANGELOG.md` | Added testing infrastructure entries |
|
|
211
|
+
|
|
212
|
+
## Verification
|
|
213
|
+
|
|
214
|
+
- [x] `bundle install` — 26 gems, 122 total
|
|
215
|
+
- [x] `bundle exec rspec` — 351 examples, 0 failures
|
|
216
|
+
- [x] `bundle exec rubocop` — 0 offenses
|
|
217
|
+
- [x] `bundle exec reek lib/` — 0 warnings
|
|
218
|
+
- [x] `bundle exec mutant run` — 97.83% coverage (543/555 killed, 12 equivalent)
|
|
219
|
+
- [x] `.mutant.yml` auto-loaded (no `--usage` flag needed)
|
|
220
|
+
- [x] Factory_bot factories produce valid string-keyed hashes
|
|
221
|
+
- [x] All 7 fixture files parse correctly (YAML bomb blocked by `safe_load`)
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# Implement security.yml design document (P1)
|
|
2
|
+
|
|
3
|
+
**Branch**: `design_implementation`
|
|
4
|
+
**Date**: 2026-02-19 to 2026-02-21
|
|
5
|
+
**Design doc**: `conf/design/security.yml` v1.1.0
|
|
6
|
+
**Commits**: fdb73e0, 9fc7e55, 46aa519, 1d0bfb9 (+ supporting: dc42c3c, 7423856, ff3d81e)
|
|
7
|
+
|
|
8
|
+
## Motivation
|
|
9
|
+
|
|
10
|
+
Security is the foundation layer of rosett-ai. Every line of code written after this
|
|
11
|
+
document is adopted must follow its constraints. Retrofitting security is 10x
|
|
12
|
+
more expensive than building it in. This implementation establishes hard rules,
|
|
13
|
+
required tooling, and safe coding patterns that apply to ALL code in the project
|
|
14
|
+
— core, TUI, GUI, compilers, and tests.
|
|
15
|
+
|
|
16
|
+
The security design document defines 12 acceptance criteria covering: dependency
|
|
17
|
+
auditing, static analysis, safe YAML parsing, shell injection prevention, file
|
|
18
|
+
permissions, YAML bounds enforcement, ANSI sanitization, NFC normalization, and
|
|
19
|
+
secrets resolution.
|
|
20
|
+
|
|
21
|
+
## Acceptance criteria
|
|
22
|
+
|
|
23
|
+
All 12 acceptance criteria from `security.yml` are satisfied:
|
|
24
|
+
|
|
25
|
+
| # | Criterion | Evidence |
|
|
26
|
+
|---|-----------|----------|
|
|
27
|
+
| 1 | bundler-audit runs in CI and blocks merge on any known CVE | `.gitlab-ci-files/security_scan/bundler-audit.yml` (pre-existing), `.overcommit.yml` hook |
|
|
28
|
+
| 2 | ruby_audit runs in CI and blocks merge on Ruby stdlib vulnerabilities | `.gitlab-ci-files/security_scan/ruby-audit.yml`, `.overcommit.yml` RubyAudit hook |
|
|
29
|
+
| 3 | RuboCop security cops are enabled and enforced | `.rubocop.yml` requires `./lib/rubocop/rosett_ai`, cops at `Severity: error` |
|
|
30
|
+
| 4 | No instance of YAML.load exists in codebase (only safe_load) | `RosettAi/UnsafeYamlLoad` cop blocks; all call sites migrated to `RosettAi::YamlLoader.load_file` |
|
|
31
|
+
| 5 | No string-interpolated system() calls exist | `RosettAi/ShellInterpolation` cop blocks; backtick interpolation also detected |
|
|
32
|
+
| 6 | All file write operations use explicit permission setting | All `File.open` calls use mode arg (0o644 for files, 0o600 for secrets) |
|
|
33
|
+
| 7 | CI pipeline rejects code that violates any security constraint | Custom cops at `Severity: error` fail CI; bundler-audit + ruby-audit block |
|
|
34
|
+
| 8 | Custom RuboCop cops flag unsafe patterns | `UnsafeYamlLoad` + `ShellInterpolation` cops with full test suites |
|
|
35
|
+
| 9 | YAML files exceeding bounds are rejected with clear error | `YamlLoader` enforces 1 MB size, 10-level depth, 1000 key count |
|
|
36
|
+
| 10 | TUI output containing ANSI escapes from YAML is sanitized | `TextSanitizer.sanitize_for_display` at all output boundaries |
|
|
37
|
+
| 11 | Filenames and identifiers are NFC-normalized | `TextSanitizer.normalize_nfc` at input boundaries (compiler, behaviour, adopt) |
|
|
38
|
+
| 12 | Secrets are never written to disk unless explicitly configured | `SecretsResolver`: ENV > secrets file (0600); never writes secrets |
|
|
39
|
+
|
|
40
|
+
## Changes by area
|
|
41
|
+
|
|
42
|
+
### Ruby upgrade 3.3.8 to 3.3.10
|
|
43
|
+
|
|
44
|
+
**Commit**: fdb73e0
|
|
45
|
+
|
|
46
|
+
Three CVEs resolved by upgrading the Ruby runtime:
|
|
47
|
+
|
|
48
|
+
| CVE | Component | Severity |
|
|
49
|
+
|-----|-----------|----------|
|
|
50
|
+
| CVE-2025-24294 | resolv (DNS) | DoS via crafted response |
|
|
51
|
+
| CVE-2025-58767 | REXML | DoS via XML entity expansion |
|
|
52
|
+
| CVE-2025-61594 | URI | Credential leak in parsing |
|
|
53
|
+
|
|
54
|
+
Files updated: `.ruby-version`, `CLAUDE.md`, `README.md`, `doc/PACKAGING.md`,
|
|
55
|
+
`doc/USAGE.md`, `spec/rosett_ai/thor/tasks/build_package_spec.rb`.
|
|
56
|
+
|
|
57
|
+
### Shell injection elimination
|
|
58
|
+
|
|
59
|
+
**Commit**: 9fc7e55
|
|
60
|
+
|
|
61
|
+
Every `system()` call using string interpolation was replaced with array-form,
|
|
62
|
+
and every backtick command with interpolation was replaced with `IO.popen`.
|
|
63
|
+
|
|
64
|
+
**build.rb** — backtick version check to IO.popen:
|
|
65
|
+
|
|
66
|
+
```diff
|
|
67
|
+
- installed = `#{ruby_bin} -e "puts RUBY_VERSION"`.strip
|
|
68
|
+
+ installed = IO.popen([ruby_bin.to_s, '-e', 'puts RUBY_VERSION'], &:read).strip
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**build.rb** — `command_available?` shell to pure Ruby:
|
|
72
|
+
|
|
73
|
+
```diff
|
|
74
|
+
- def command_available?(cmd)
|
|
75
|
+
- system("command -v #{cmd} > /dev/null 2>&1")
|
|
76
|
+
+ def command_available?(cmd)
|
|
77
|
+
+ ENV.fetch('PATH', '').split(File::PATH_SEPARATOR).any? do |dir|
|
|
78
|
+
+ File.executable?(File.join(dir, cmd))
|
|
79
|
+
+ end
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**compressor.rb** — xz pipeline to array-form:
|
|
83
|
+
|
|
84
|
+
```diff
|
|
85
|
+
- xz_cmd = "xz#{" -#{level}" if level} --stdout #{tar_path} > #{output_path}"
|
|
86
|
+
- success = system(xz_cmd)
|
|
87
|
+
+ xz_args = ['xz']
|
|
88
|
+
+ xz_args.push("-#{level}") if level
|
|
89
|
+
+ xz_args.push('--stdout', tar_path)
|
|
90
|
+
+ success = File.open(output_path, 'wb', 0o644) do |out|
|
|
91
|
+
+ system(*xz_args, out: out)
|
|
92
|
+
+ end
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### File permissions enforcement
|
|
96
|
+
|
|
97
|
+
**Commit**: 9fc7e55
|
|
98
|
+
|
|
99
|
+
All `File.write` and `File.open(..., 'w')` calls now use explicit permission
|
|
100
|
+
mode arguments:
|
|
101
|
+
|
|
102
|
+
| File | Permission | Rationale |
|
|
103
|
+
|------|-----------|-----------|
|
|
104
|
+
| Build log | 0o644 | Non-sensitive output |
|
|
105
|
+
| Compiled rules | 0o644 | User configuration files |
|
|
106
|
+
| Lockfile | 0o644 | Version tracking metadata |
|
|
107
|
+
| Behaviour temp files | 0o644 | Temporary editor copies |
|
|
108
|
+
| Adopt cache | 0o644 | Non-sensitive cache |
|
|
109
|
+
| Compressed archives | 0o644 | User-accessible output |
|
|
110
|
+
| Secrets file | 0o600 | Contains API keys (validated by `SecretsResolver`) |
|
|
111
|
+
|
|
112
|
+
### Custom RuboCop cops
|
|
113
|
+
|
|
114
|
+
**Commit**: 46aa519
|
|
115
|
+
|
|
116
|
+
Two custom cops enforce security rules statically:
|
|
117
|
+
|
|
118
|
+
**`RosettAi/UnsafeYamlLoad`** (`lib/rubocop/cop/rosett-ai/unsafe_yaml_load.rb`):
|
|
119
|
+
|
|
120
|
+
```ruby
|
|
121
|
+
def_node_matcher :unsafe_yaml_load?, <<~PATTERN
|
|
122
|
+
(send (const {nil? cbase} :YAML) {:load :load_file} ...)
|
|
123
|
+
PATTERN
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Flags `YAML.load` and `YAML.load_file`. Severity: error (blocks CI).
|
|
127
|
+
|
|
128
|
+
**`RosettAi/ShellInterpolation`** (`lib/rubocop/cop/rosett-ai/shell_interpolation.rb`):
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
def_node_matcher :shell_with_interpolation?, <<~PATTERN
|
|
132
|
+
(send nil? {:system :exec :spawn} dstr ...)
|
|
133
|
+
PATTERN
|
|
134
|
+
|
|
135
|
+
def on_xstr(node)
|
|
136
|
+
return unless node.children.any?(&:begin_type?)
|
|
137
|
+
add_offense(node, message: BACKTICK_MSG)
|
|
138
|
+
end
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Flags `system("cmd #{arg}")`, `exec("#{cmd}")`, `spawn("#{x}")`, and backtick
|
|
142
|
+
interpolation `` `#{cmd} --flag` ``. Severity: error (blocks CI).
|
|
143
|
+
|
|
144
|
+
### YAML bounds enforcement (YamlLoader)
|
|
145
|
+
|
|
146
|
+
**Commit**: 46aa519
|
|
147
|
+
|
|
148
|
+
`lib/rosett_ai/yaml_loader.rb` enforces three bounds before returning parsed data:
|
|
149
|
+
|
|
150
|
+
| Bound | Limit | Check method |
|
|
151
|
+
|-------|-------|-------------|
|
|
152
|
+
| File size | 1 MB (1,048,576 bytes) | `check_file_size!` — before parsing |
|
|
153
|
+
| Nesting depth | 10 levels | `check_depth!` — recursive walk |
|
|
154
|
+
| Key count | 1,000 keys | `check_key_count!` — recursive count |
|
|
155
|
+
|
|
156
|
+
All existing YAML.safe_load call sites were migrated to use `YamlLoader.load_file`:
|
|
157
|
+
`rule_adopter.rb`, `behaviour_compiler.rb`, `behaviour.rb` (5 call sites),
|
|
158
|
+
`behaviour_validator.rb`.
|
|
159
|
+
|
|
160
|
+
### ANSI sanitization (TextSanitizer)
|
|
161
|
+
|
|
162
|
+
**Commit**: 46aa519
|
|
163
|
+
|
|
164
|
+
`lib/rosett_ai/text_sanitizer.rb` provides two sanitization methods:
|
|
165
|
+
|
|
166
|
+
**`strip_ansi`** — removes ANSI CSI sequences (`\e[...X`), OSC sequences
|
|
167
|
+
(`\e]...\a`), and non-printable control characters (except tab, newline, CR):
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
ANSI_PATTERN = /\e\[\d*(?:;\d*)*[A-Za-z]|\e\][^\a]*\a|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**`sanitize_for_display`** — recursively strips ANSI from strings, hashes,
|
|
174
|
+
and arrays. Integrated at display boundaries in `adopt.rb` and `behaviour.rb`.
|
|
175
|
+
|
|
176
|
+
### NFC normalization
|
|
177
|
+
|
|
178
|
+
**Commit**: 46aa519
|
|
179
|
+
|
|
180
|
+
`TextSanitizer.normalize_nfc` applies Unicode NFC normalization at input
|
|
181
|
+
boundaries. Used in:
|
|
182
|
+
|
|
183
|
+
- `behaviour_compiler.rb` — filename normalization before key generation
|
|
184
|
+
- `behaviour.rb` — name parameters in add/modify/delete/show/validate commands
|
|
185
|
+
- `rule_adopter.rb` — sensitive source filename comparison
|
|
186
|
+
|
|
187
|
+
### Secrets resolution (SecretsResolver)
|
|
188
|
+
|
|
189
|
+
**Commit**: bf9f15e
|
|
190
|
+
|
|
191
|
+
`lib/rosett_ai/secrets_resolver.rb` resolves secrets through an ordered fallback:
|
|
192
|
+
|
|
193
|
+
1. Environment variable (`ENV.fetch`)
|
|
194
|
+
2. Secrets file (`~/.config/rosett-ai/secrets.yml` with 0600 permissions)
|
|
195
|
+
3. Raise with instructions if not found
|
|
196
|
+
|
|
197
|
+
Permission validation rejects secrets files without 0600 permissions.
|
|
198
|
+
|
|
199
|
+
### Backtick interpolation detection
|
|
200
|
+
|
|
201
|
+
**Commit**: 1d0bfb9
|
|
202
|
+
|
|
203
|
+
Extended the `ShellInterpolation` cop with `on_xstr` handler to detect
|
|
204
|
+
backtick interpolation (`` `#{cmd}` ``), not just `system()`/`exec()`/`spawn()`.
|
|
205
|
+
|
|
206
|
+
## Secrets resolution flow
|
|
207
|
+
|
|
208
|
+
```mermaid
|
|
209
|
+
flowchart TD
|
|
210
|
+
START[resolve env_key] --> ENV{ENV variable<br/>set and non-empty?}
|
|
211
|
+
ENV -->|yes| RETURN_ENV[Return ENV value]
|
|
212
|
+
ENV -->|no| FILE{Secrets file<br/>exists?}
|
|
213
|
+
FILE -->|no| ERROR[Raise: key not found<br/>with setup instructions]
|
|
214
|
+
FILE -->|yes| PERMS{Permissions<br/>== 0600?}
|
|
215
|
+
PERMS -->|no| PERMS_ERROR[Raise: insecure permissions<br/>expected 0600]
|
|
216
|
+
PERMS -->|yes| YAML[Load via YamlLoader]
|
|
217
|
+
YAML --> KEY{Key in<br/>YAML data?}
|
|
218
|
+
KEY -->|yes| RETURN_FILE[Return file value]
|
|
219
|
+
KEY -->|no| ERROR
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## YAML bounds pipeline
|
|
223
|
+
|
|
224
|
+
```mermaid
|
|
225
|
+
flowchart LR
|
|
226
|
+
INPUT[YAML file path] --> SIZE{File size<br/>≤ 1 MB?}
|
|
227
|
+
SIZE -->|no| REJECT1[Raise: exceeds size limit]
|
|
228
|
+
SIZE -->|yes| PARSE[YAML.safe_load_file<br/>permitted: Date, Time]
|
|
229
|
+
PARSE --> DEPTH{Nesting<br/>≤ 10 levels?}
|
|
230
|
+
DEPTH -->|no| REJECT2[Raise: exceeds depth limit]
|
|
231
|
+
DEPTH -->|yes| KEYS{Key count<br/>≤ 1000?}
|
|
232
|
+
KEYS -->|no| REJECT3[Raise: exceeds key limit]
|
|
233
|
+
KEYS -->|yes| RETURN[Return parsed data]
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Files created
|
|
237
|
+
|
|
238
|
+
| File | Purpose |
|
|
239
|
+
|------|---------|
|
|
240
|
+
| `lib/rosett_ai/yaml_loader.rb` | Centralized YAML loading with bounds |
|
|
241
|
+
| `lib/rosett_ai/text_sanitizer.rb` | ANSI stripping + NFC normalization |
|
|
242
|
+
| `lib/rosett_ai/secrets_resolver.rb` | Multi-source secret resolution |
|
|
243
|
+
| `lib/rubocop/cop/rosett-ai/shell_interpolation.rb` | Shell injection detection cop |
|
|
244
|
+
| `lib/rubocop/cop/rosett-ai/unsafe_yaml_load.rb` | Unsafe YAML.load detection cop |
|
|
245
|
+
| `lib/rubocop/rosett_ai.rb` | Cop loader |
|
|
246
|
+
| `spec/rosett_ai/yaml_loader_spec.rb` | YamlLoader unit tests |
|
|
247
|
+
| `spec/rosett_ai/text_sanitizer_spec.rb` | TextSanitizer unit tests |
|
|
248
|
+
| `spec/rosett_ai/secrets_resolver_spec.rb` | SecretsResolver unit tests (167 lines) |
|
|
249
|
+
| `spec/rubocop/cop/rosett-ai/shell_interpolation_spec.rb` | ShellInterpolation cop tests |
|
|
250
|
+
| `spec/rubocop/cop/rosett-ai/unsafe_yaml_load_spec.rb` | UnsafeYamlLoad cop tests |
|
|
251
|
+
| `.gitlab-ci-files/security_scan/ruby-audit.yml` | Ruby stdlib CVE scanning CI job |
|
|
252
|
+
|
|
253
|
+
## Files modified
|
|
254
|
+
|
|
255
|
+
| File | Change |
|
|
256
|
+
|------|--------|
|
|
257
|
+
| `lib/rosett_ai/thor/tasks/build.rb` | Backtick to IO.popen, shell to pure Ruby, File.open with perms |
|
|
258
|
+
| `lib/rosett_ai/backup/compressor.rb` | Shell pipeline to array-form, File.open with perms |
|
|
259
|
+
| `lib/rosett_ai/thor/tasks/behaviour.rb` | YamlLoader + TextSanitizer integration (5 call sites) |
|
|
260
|
+
| `lib/rosett_ai/thor/tasks/adopt.rb` | TextSanitizer for display, SecretsResolver for API key |
|
|
261
|
+
| `lib/rosett_ai/adopter/rule_adopter.rb` | YamlLoader for cache/redactions, TextSanitizer for filenames |
|
|
262
|
+
| `lib/rosett_ai/compiler/behaviour_compiler.rb` | YamlLoader + NFC filename normalization |
|
|
263
|
+
| `lib/rosett_ai/validators/behaviour_validator.rb` | YamlLoader with ValidationError handling |
|
|
264
|
+
| `lib/rosett_ai/thor/tasks/compile.rb` | File.open with explicit permissions |
|
|
265
|
+
| `.rubocop.yml` | Added custom cop require + enforcement at Severity: error |
|
|
266
|
+
| `.overcommit.yml` | Added RubyAudit hook |
|
|
267
|
+
| `.ruby-version` | 3.3.8 to 3.3.10 |
|
|
268
|
+
| `Gemfile` | Added ruby_audit, flog |
|
|
269
|
+
|
|
270
|
+
## Verification
|
|
271
|
+
|
|
272
|
+
- [x] `bundle exec ruby-audit check` — 0 vulnerabilities
|
|
273
|
+
- [x] `bundle exec bundler-audit check` — 0 vulnerabilities
|
|
274
|
+
- [x] `bundle exec rubocop` — 0 offenses (custom cops active)
|
|
275
|
+
- [x] `bundle exec reek lib/` — 0 warnings
|
|
276
|
+
- [x] `bundle exec rspec` — 437 examples, 0 failures
|
|
277
|
+
- [x] `bundle exec mutant run` — 97.83% kill rate
|
|
278
|
+
- [x] No `YAML.load` calls in codebase (cop enforced)
|
|
279
|
+
- [x] No interpolated system/exec/spawn/backtick calls (cop enforced)
|
|
280
|
+
- [x] All File.open/File.write use explicit permissions
|
|
281
|
+
- [x] SecretsResolver validates 0600 on secrets file
|