chronos-ruby 1.2.1 → 1.2.2

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.
@@ -1,37 +1,50 @@
1
1
  # Compatibility
2
2
 
3
- Chronos Ruby 1.2.1 is the current stable release of the transitional line for modern Ruby, Rails, and Sidekiq applications. Individual combinations remain `Best effort` until their dedicated CI and real-application gates pass; a stable package version does not by itself promote an unverified combination to `Supported`. Technical compatibility does not make an end-of-life Ruby, Rails, Rack, or Sidekiq release secure. Versions 1.0 and 1.1 remain the frozen legacy line.
3
+ Chronos Ruby 1.2.2 is prepared as a maintenance release of the transitional line for modern Ruby, Rails, and Sidekiq applications. Individual combinations remain `Best effort` until their dedicated CI and real-application gates pass; a stable package version does not by itself promote an unverified combination to `Supported`. Technical compatibility does not make an end-of-life Ruby, Rails, Rack, or Sidekiq release secure. Versions 1.0 and 1.1 remain the frozen legacy line.
4
4
 
5
5
  ## Core and Rack
6
6
 
7
- | Ruby | Status | Evidence |
8
- |---|---|---|
9
- | 2.7–3.2 | Best effort | Transitional matrix introduced in this candidate; promotion requires green CI evidence |
10
- | 3.3–3.4 | Best effort | Core checks run in modern CI; Rails 8-specific behavior belongs to 2.x |
11
- | Earlier than 2.7 | Unsupported in 1.2 | Use an appropriate frozen legacy release |
7
+ | Ruby | Status | Evidence |
8
+ | ---------------- | ------------------ | -------------------------------------------------------------------------------------- |
9
+ | 2.7–3.2 | Best effort | Local core/security/package gates passed; remote CI/promotion evidence remains required |
10
+ | 3.3–3.4 | Best effort | Local core/package gates passed; Rails 8 belongs to 2.x |
11
+ | Earlier than 2.7 | Unsupported in 1.2 | Use an appropriate frozen legacy release |
12
12
 
13
13
  ## Rails
14
14
 
15
- | Ruby | Rails | Status | Evidence |
16
- |---|---|---|---|
17
- | 2.7–3.2 | 7.0–7.1 | Best effort | Real Rails applications and the complete integration gate are pending |
18
- | 3.1–3.4 | 7.2 | Best effort | Feature and package checks; use a validated application combination before production rollout |
19
- | Rails 4–6 | — | Unsupported in 1.2 | Use a matching earlier Chronos release |
15
+ | Ruby | Rails | Status | Evidence |
16
+ | --------- | ------- | ------------------ | --------------------------------------------------------------------------------------------- |
17
+ | 2.7–3.2 | 7.0–7.1 | Best effort | Real Rails applications and the complete integration gate are pending |
18
+ | 3.1–3.4 | 7.2 | Best effort | Feature and package checks; use a validated application combination before production rollout |
19
+ | Rails 4–6 | — | Unsupported in 1.2 | Use a matching earlier Chronos release |
20
20
 
21
21
  ## Sidekiq
22
22
 
23
- | Ruby | Sidekiq | Status | Evidence |
24
- |---|---|---|---|
25
- | 2.7–3.4 | 7.x | Best effort | Real Sidekiq 7 smoke applications are pending |
26
- | Sidekiq 4–6 | — | Unsupported in 1.2 | Use a matching earlier Chronos release |
23
+ | Ruby | Sidekiq | Status | Evidence |
24
+ | ----------- | ------- | ------------------ | --------------------------------------------- |
25
+ | 2.7–3.4 | 7.x | Best effort | Pinned 7.3.10 middleware-chain fixture; Redis-backed worker lifecycle is a separate gate |
26
+ | Sidekiq 4–6 | — | Unsupported in 1.2 | Use a matching earlier Chronos release |
27
27
 
28
28
  Active Job uses the public `serialize`, `deserialize`, and `perform_now` extension points with a bounded namespaced field. Support follows the validated Rails pairs above. Adapters that bypass these hooks require their own evidence.
29
29
 
30
- Release evidence is recorded in [Version 1.2 readiness](release-1.2-readiness.md). Historical evidence remains in [Version 1.0 readiness](release-1.0-readiness.md) and [Version 1.1 readiness](release-1.1-readiness.md).
30
+ Release evidence is recorded in [Version 1.2.2 readiness](release-1.2.2-readiness.md). Historical evidence remains in [Version 1.0 readiness](release-1.0-readiness.md) and [Version 1.1 readiness](release-1.1-readiness.md).
31
31
 
32
32
  Status meanings:
33
33
 
34
34
  - Supported: every mandatory compatibility gate for the exact pair passes.
35
- - Best effort: intended to work but missing a complete gate; no 1.1 pair is advertised this way.
35
+ - Best effort: intended to work but missing a complete gate; passing focused smokes alone does not promote a pair.
36
36
  - Deprecated: still tested while removal is planned.
37
- - Unsupported: outside the tested 1.1 contract.
37
+ - Unsupported: outside the declared transitional contract.
38
+
39
+ ## Selected transitional smoke matrix
40
+
41
+ | Framework patch | Ruby versions | Scope |
42
+ | --- | --- | --- |
43
+ | Rails 7.0.10 | 2.7, 3.0 | Real app boot, controller/SQL, Active Job, Error Reporter, deploy generator |
44
+ | Rails 7.1.6 | 2.7, 3.1, 3.2 | Same application checks |
45
+ | Rails 7.2.4 | 3.1, 3.3, 3.4 | Same checks; Ruby >= 3.1 is required by Rails |
46
+ | Sidekiq 7.3.10 | 2.7, 3.0, 3.1, 3.2, 3.3, 3.4 | Actual client/server middleware chains; no Redis or worker process |
47
+
48
+ Framework patches are pinned in the fixture Gemfiles. Dependency locks and execution logs must be retained for promotion; PR/release CI uploads resolved lockfiles. Ruby images in the local helper follow the selected minor and print their exact runtime patch. These are technical compatibility checks, not an assertion of upstream security maintenance. See [official Rails requirements](https://guides.rubyonrails.org/v7.2.0/upgrading_ruby_on_rails.html#ruby-versions).
49
+
50
+ The local 1.2.2 runs passed every selected smoke above. Status remains Best effort because these focused gates do not close provider/SaaS/Redis-backed lifecycle and final promotion requirements.
@@ -0,0 +1,35 @@
1
+ # Transitional deploy CLI
2
+
3
+ Version 1.2.2 ports the CLI/security contract from legacy 1.1.4 while preserving Ruby >= 2.7 and < 3.5 and the existing transitional integrations. After the release is published and installed:
4
+
5
+ ```sh
6
+ export CHRONOS_PROJECT_ID=your-project
7
+ # Inject CHRONOS_PROJECT_KEY through the deployment secret mechanism.
8
+ export CHRONOS_ENVIRONMENT=production
9
+ export CHRONOS_DEPLOY_ID=stable-publication-identity
10
+ export CHRONOS_DEPLOY_REVISION=revision-of-the-actually-published-artifact
11
+ bundle exec chronos deploy notify
12
+ ```
13
+
14
+ No Ruby notification script needs to be copied. The CLI does not boot Rails or read credentials/initializers implicitly. CHRONOS_SERVICE_NAME must match runtime service configuration; CHRONOS_DEPLOY_VERSION can replace the revision. The default host is https://chronosmonitor.com.br; CHRONOS_HOST overrides it with HTTPS only, without URL credentials, query or fragment.
15
+
16
+ Canonical variables: CHRONOS_PROJECT_ID, CHRONOS_PROJECT_KEY, CHRONOS_HOST, CHRONOS_ENVIRONMENT, CHRONOS_SERVICE_NAME, CHRONOS_DEPLOY_ID, CHRONOS_DEPLOY_REVISION, CHRONOS_DEPLOY_VERSION. Aliases: DEPLOY_ENVIRONMENT, SERVICE_NAME, DEPLOY_ID, GIT_SHA, APP_VERSION. Conflicting aliases are errors. Explicit public options override ENV, which overrides optional config/chronos-deploy.yml. The key is ENV-only. YAML accepts only string values for project_id, host, environment, service_name, deploy_id, revision and version; never persist secrets there.
17
+
18
+ `deploy diagnose --offline` validates complete notify configuration locally without authenticating credentials. `deploy verify` uses the existing correlated receipt and sends no deploy or dependency inventory. A receipt is not proof of SaaS projection. Help/version are local. `--timeout` accepts 0 < seconds <= 60 and bounds transport/flush operations, not one global process deadline. Retries/backlog are disabled for this short-lived CLI. Notify can still produce dependency events.
19
+
20
+ Operational JSON uses schema_version=1, success and state; verify also exposes verification_status. Exit codes: 0 local/confirmed success, 1 invalid configuration, 2 unconfirmed delivery, 3 internal failure. `--on-error=warn` converts only unconfirmed delivery to process status 0 and retains success=false. Do not retry blindly: the receiver may already have accepted an event. A stable deployment ID does not guarantee exactly-once event consumption or billing.
21
+
22
+ Generate integrations explicitly:
23
+
24
+ ```sh
25
+ bundle exec chronos deploy install --provider kamal --dry-run
26
+ bundle exec chronos deploy install --provider kamal
27
+ bundle exec chronos deploy install --provider kamal --check
28
+ bundle exec rails generate chronos:deploy:install --provider=kamal
29
+ ```
30
+
31
+ Providers: kamal, capistrano, github-actions, gitlab-ci, jenkins and azure-devops. Use --root for an explicit destination. Equal content is unchanged; conflicts preserve customer files and expose only the proposed template. Merge manually before considering --force. POSIX installation (Linux/macOS) uses descriptor-relative operations, bounded reads and no-follow opens to prevent symlink redirection. No pipeline is activated automatically and no Gemfile/lockfile/dependency is modified by the installer.
32
+
33
+ Kamal creates .kamal/hooks/post-deploy; Capistrano creates lib/capistrano/tasks/chronos_deploy.rake and runs locally after deploy:published. Do not also load chronos/capistrano. CI fragments are generated in config/chronos/deploy for manual merge after the real successful publication; explicitly export its actual identity/revision outputs. The GitLab fragment is hidden and must be incorporated into an existing job. A modern deployment runner does not automatically belong to this gem's Ruby matrix.
34
+
35
+ Use [transitional Docker tests](../README.md#tests) and [1.2.2 readiness](release-1.2.2-readiness.md). The historical legacy ADR describes the origin; current publication and capability decisions belong to this release's readiness document. SaaS recipe selection must use explicit per-line capabilities and retain fallback for older clients. Rails boot/credentials integration and the Ruby 4/Rails 8 line remain separate work.
@@ -0,0 +1,59 @@
1
+ # Preparar o ambiente de testes legado no macOS
2
+
3
+ Este procedimento testa a trilha `legacy/1`. Não publica a gem nem usa credenciais reais do SaaS.
4
+
5
+ ## 1. Iniciar Docker Desktop
6
+
7
+ Instale ou atualize a edição correspondente ao processador do Mac, conforme a [documentação oficial](https://docs.docker.com/desktop/setup/install/mac-install/). Abra o aplicativo e aguarde o engine iniciar:
8
+
9
+ ```sh
10
+ open -a Docker
11
+ docker version
12
+ docker info
13
+ ```
14
+
15
+ `docker version` precisa mostrar Client e Server. Se aparecer erro no socket ou “Cannot connect”, o engine ainda não está disponível. Confirme o contexto com `docker context ls`; selecione o contexto local do Docker Desktop caso outro contexto esteja ativo.
16
+
17
+ ## 2. Entrar no repositório e executar o primeiro Ruby
18
+
19
+ ```sh
20
+ cd /Users/antoniojefferson/Develop/Projects/Rails/chronos-ruby
21
+ sh script/test_legacy_docker 2.2.10
22
+ ```
23
+
24
+ O helper usa linux/amd64 explicitamente para uniformizar as imagens antigas em Mac Intel e Apple Silicon. A [documentação de plataformas do Docker](https://docs.docker.com/build/building/multi-platform/) explica a emulação; ela pode ser mais lenta.
25
+
26
+ O repositório é montado somente para leitura. O helper copia os arquivos para /work no container, exclui configuração local do Bundler, .git, .aws e .env, e remove somente os lockfiles dessa cópia. Isso é necessário porque o lockfile atual do repositório foi gerado com ferramentas modernas e não corresponde ao gemspec legado. Nenhum lockfile local é alterado.
27
+
28
+ O container instala Bundler 1.17.3 e resolve as dependências do gemspec, incluindo Rake 12.3, RuboCop 0.49 e Minitest 5.10.3. O script executa a suíte existente, os testes da CLI/adaptadores/receptor HTTPS e o smoke da gem instalada. Certificado de teste é gerado temporariamente e explicitamente confiado; verificação TLS não é desligada.
29
+
30
+ ## 3. Executar a matriz Ruby completa
31
+
32
+ ```sh
33
+ for version in 2.2.10 2.3.8 2.4.10 2.5.9 2.6.10; do
34
+ sh script/test_legacy_docker "$version" || break
35
+ done
36
+ ```
37
+
38
+ Pare no primeiro erro e preserve o log. Uma execução interrompida não aprova os demais Rubies. Registre por imagem: versão Ruby, OpenSSL/JSON, resultado da suíte e resultado do pacote instalado.
39
+
40
+ ## 4. Executar os pares Rails suportados
41
+
42
+ ```sh
43
+ sh script/test_legacy_docker 2.2.10 examples/rails-4.2
44
+ sh script/test_legacy_docker 2.3.8 examples/rails-4.2
45
+ sh script/test_legacy_docker 2.5.9 examples/rails-5.2
46
+ sh script/test_legacy_docker 2.6.10 examples/rails-5.2
47
+ ```
48
+
49
+ Esses comandos executam o smoke existente da aplicação e o novo smoke do generator de deploy, em diretório temporário. Eles não ampliam os pares suportados.
50
+
51
+ ## 5. Tratar falhas de preparação
52
+
53
+ - Falha no pull ou acesso a rubygems.org: verificar conexão, proxy e disponibilidade do registro.
54
+ - Erro de certificado/OpenSSL nas imagens antigas: registrar imagem e mensagem, corrigir CA ou preparar imagem derivada com certificados confiáveis compatíveis. Não usar HTTP, ssl_verify=false ou mirrors sem validação TLS.
55
+ - Dependência incompatível: identificar a dependência e fixar versão compatível na definição da imagem/matriz antes de repetir. Não modernizar o gemspec legado para fazer o teste passar.
56
+ - Erro de arquitetura: confirmar execução linux/amd64 e suporte de emulação no Docker Desktop.
57
+ - Erro RuboCop/RSpec/CLI: é resultado da validação do código, não falha de instalação; corrigir antes de promover a release.
58
+
59
+ O helper prepara execuções isoladas, mas ainda não substitui imagens reproduzíveis fixadas por digest e dependências resolvidas por runtime. A aprovação final da matriz deve preservar esses registros. Depois dos gates locais, permanecem os testes SaaS de receipt/ingestão/ledger/projeção e os smokes autorizados dos fornecedores.
@@ -1,6 +1,6 @@
1
- # Rails 4.2 and 5.2 legacy integration
1
+ # Rails 7 transitional integration
2
2
 
3
- Version 0.5 adds an explicit Rails entry point:
3
+ Version 0.5 introduced the explicit Rails entry point. In the 1.2 line it targets the documented Rails 7 combinations; Rails 4/5 belong to the legacy 1.1 line:
4
4
 
5
5
  ```ruby
6
6
  require "chronos/rails"
@@ -1,23 +1,20 @@
1
- # Sidekiq 4/5 legacy integration
1
+ # Sidekiq 7 transitional integration
2
2
 
3
- Version `0.6.0.pre.1` starts the legacy jobs line with optional Sidekiq 4 and 5 client and server middleware. Load it after declaring Sidekiq:
3
+ The historical integration began in `0.6.0.pre.1`. The current 1.2.2 line targets Sidekiq 7 through its public middleware APIs. Sidekiq 4/5 consumers must use a matching legacy release. Load the optional integration after declaring Sidekiq:
4
4
 
5
5
  ```ruby
6
- gem "sidekiq", "~> 5.0"
7
- gem "chronos-ruby", "~> 1.1.0", :require => "chronos/sidekiq"
6
+ gem "sidekiq", "~> 7.3"
7
+ gem "chronos-ruby", "~> 1.2.2", :require => "chronos/sidekiq"
8
8
  ```
9
9
 
10
10
  `chronos/sidekiq` installs middleware through the public `configure_client` and `configure_server` APIs. It does nothing when Sidekiq is unavailable, and the core gem never requires Sidekiq. Installation adds no Chronos thread or Redis/database connection per job; delivery continues through the agent's existing fixed worker pool.
11
11
 
12
- The client middleware adds a top-level `chronos` metadata object to the Sidekiq envelope. It never changes the public `args` array. Only `trace_id` and `request_id` propagate, with schema version and enqueue time. The server scopes that context for the worker, measures duration and calculable queue latency, and emits one `job` event.
12
+ The client middleware adds a top-level `chronos` metadata object to the Sidekiq envelope. It never changes the public `args` array. Only bounded trace/span/request identifiers and flags propagate, with schema version and enqueue time. The server scopes that context for the worker, measures duration and calculable queue latency, and emits one `job` event.
13
13
 
14
14
  Collected Sidekiq fields are worker class, queue, JID, retry count, duration, queue latency, status, error class, bounded tags, and bounded arguments. At most 20 top-level arguments, 20 values per nested collection, four levels, and 512 bytes per string are traversed. Arguments then pass through the standard sensitive-key/content sanitizer before queueing. Limits reduce exposure and resource use but do not make unnecessary personal data safe to collect.
15
15
 
16
16
  On failure, the server middleware records a failed-job observation, invokes `notify_once`, and re-raises the identical exception so Sidekiq retains ownership of retries and failure handlers. A shared execution marker prevents a nested Active Job hook and the Sidekiq middleware from sending the same exception twice. Version 0.7 aggregates the job observation; the exception retains sanitized bounded arguments. Chronos does not install an additional global Sidekiq error handler.
17
17
 
18
- Known limitations for this first 0.6 prerelease:
18
+ ## Validation scope
19
19
 
20
- - the compatibility contract uses Sidekiq 4/5 public middleware signatures; dedicated real Sidekiq matrix jobs must pass before status becomes `Supported`;
21
- - Active Job propagation, Resque, and Delayed Job are subsequent 0.6 increments;
22
- - argument collection is automatic in this prerelease; applications should avoid placing secrets or unnecessary personal data in job arguments;
23
- - context propagation is limited to trace and request identifiers.
20
+ The pinned Sidekiq 7.3.10 fixture exercises actual middleware chains, argument preservation, context propagation, job telemetry and error re-raising across Ruby 2.7–3.4. It does not start Redis or a worker process. Redis-backed lifecycle evidence remains a separate promotion gate. See [Compatibility](../compatibility.md) and [1.2.2 readiness](../release-1.2.2-readiness.md).
@@ -0,0 +1,44 @@
1
+ # Version 1.1.4 release preparation
2
+
3
+ Version `1.1.4` prepares deploy automation for the stable legacy line. It packages the `chronos` CLI, explicit ENV/YAML configuration, optional provider installers and Rails/Rake adapters. It preserves Ruby >= 2.2.10 and < 2.7, the existing Rails/Sidekiq pairs, and protocol `schema_version: "1.0"`. This is a limited maintenance exception; it does not reopen general feature development on the legacy line.
4
+
5
+ ## Operational contract
6
+
7
+ - No notification, generator execution, or application file changes on install, require, help, or version.
8
+ - Notify requires explicit project credentials, environment, stable publication ID, and actual published revision/version. It uses `Chronos.notify_deploy` and always closes the agent.
9
+ - Verify reuses `Chronos.verify_integration`, validates its receipt, disables dependency reporting, and does not create a deploy. A receipt is not proof of SaaS projection.
10
+ - CLI transport requires verified HTTPS. Retries and backlog are disabled for this short-lived process; transport and flush operations have bounded timeouts, not a single global process deadline.
11
+ - Exit codes: 0 confirmed/local success, 1 invalid configuration, 2 unconfirmed delivery, 3 internal failure. Warning policy only converts code 2 to 0 and retains `success=false`.
12
+ - Repeated notify calls keep deployment identity but can generate another event and consumption. Dependency events can add consumption. Do not retry blindly after an unconfirmed result.
13
+ - Installers preserve conflicting files, offer a privacy-safe proposed template, and reject destination symlinks. POSIX descriptor-relative operations prevent parent-path swaps from redirecting creation or publication. CI fragments require manual merge after the actual publication.
14
+
15
+ ## Evidence and pending gates
16
+
17
+ Local Ruby 3.4 checks are supplementary evidence only. They do not establish compatibility with the supported legacy runtimes.
18
+
19
+ Before tagging, require:
20
+
21
+ - core suite, CLI, transport, privacy, shutdown/fork, and RuboCop 0.49 gates on Ruby 2.2.10, 2.3.8, 2.4.10, 2.5.9, and 2.6.10;
22
+ - Rails 4.2 on Ruby 2.2.10/2.3.8 and Rails 5.2 on Ruby 2.5.9/2.6.10, including deploy generator smoke;
23
+ - Sidekiq 4.2.10 on Ruby 2.2.10 and Sidekiq 5.2.10 on Ruby 2.5.9;
24
+ - built gem installed into clean supported runtimes, executable/help/offline diagnostics and HTTPS notification/receipt tests;
25
+ - dependency audit with the CI-pinned tooling, documentation and PR-body verification;
26
+ - query-analysis/Rack benchmarks and fake-endpoint load/privacy gates;
27
+ - authorized provider smoke tests and SaaS evidence for receipt, ingestion, identity, consumption, and projection; keep these distinct;
28
+ - reviewed source, package inspection, checksum, SPDX SBOM, and maintainer approval.
29
+
30
+ Use the [Docker test instructions](../README.md#tests). The working-tree lockfile originated from modern tooling; the isolated Docker helper resolves dependencies only in its container copy. It does not prove the direct CI workflow's dependency resolution will pass; that remains a required check.
31
+
32
+ ## SaaS rollout and transitional backport
33
+
34
+ The CLI capability is introduced by legacy release `1.1.4`. The transitional main-line backport is not included in this release. The SaaS must select recipes using explicitly validated release ranges for each line, never a single cross-line minimum. Keep old script recipes for older clients and do not delete customer-edited scripts.
35
+
36
+ ## Publication
37
+
38
+ No release has been published by this preparation. Never replace previously published artifacts. After all gates and approval, the release tag must be exactly `v1.1.4` and match `Chronos::VERSION`. Pushing that tag triggers the release workflow and its publication job; do not create or push it before approval. Publish through the configured Trusted Publishing workflow rather than a manual gem push.
39
+
40
+ ## Security regression coverage
41
+
42
+ `spec/security/` covers SQL literal confidentiality through the final APM envelope, complete original-query validation before inspection, sensitive symbols through final serialization, cyclic/deep diagnostic input and caller ownership, and deterministic symlink/concurrent-file interleavings. Conservative SQL handling omits oversized or ambiguous queries; plan evidence remains best effort. The installer uses fixed POSIX calls through standard-library Fiddle and does not execute shell commands. Unsupported filesystem backends must fail closed.
43
+
44
+ Security-fix validation: the core/security suite passed on MRI 2.2.10, 2.3.8, 2.4.10, 2.5.9 and 2.6.10 in isolated Linux/amd64 containers: 221 RSpec examples, the complete RuboCop 0.49.1 inspection (225 files), 25 standalone source tests (127 assertions), gem build/install, local diagnostics, and six installed-executable HTTPS tests (36 assertions) per runtime. The expanded installed-gem smoke also verifies explicit Kamal hook generation and idempotent check in a path containing spaces. These results do not close the separate Rails/Sidekiq, supplier, SaaS, audit, benchmark, or remote CI gates.
@@ -0,0 +1,27 @@
1
+ # Version 1.2.2 release preparation
2
+
3
+ This release ports packaged deployment automation and the five security corrections from legacy 1.1.4 into the transitional Ruby >= 2.7 and < 3.5 line. It preserves the main-line integrations and protocol v1. It does not introduce Ruby 4, Rails 8, or the planned 2.x line.
4
+
5
+ The gates include core/security/lint, standalone CLI/adapters, real Rails 7 applications, real Sidekiq 7 middleware chains, installed-package tests, documentation, audit, benchmarks, and maintainer approval. Local execution evidence is recorded below as verification proceeds; a configured workflow is not proof of success.
6
+
7
+ Rails fixtures pin railties/actionpack/activejob/activerecord 7.0.10, 7.1.6 and 7.2.4. Sidekiq is pinned to 7.3.10. Official RubyGems metadata reports Ruby >= 2.7 for Rails 7.0/7.1 and Sidekiq 7.3.10, and >= 3.1 for Rails 7.2.4. See [Rails requirements](https://guides.rubyonrails.org/v7.2.0/upgrading_ruby_on_rails.html#ruby-versions), [Rails 7.2.4 metadata](https://rubygems.org/gems/rails/versions/7.2.4), and [Sidekiq metadata](https://rubygems.org/gems/sidekiq/versions/7.3.10).
8
+
9
+ The Sidekiq smoke exercises its actual public middleware chains, context, preserved arguments, and error propagation; it does not start Redis or a worker process. The Rails smoke boots a real app with SQLite, controller/SQL notifications, Active Job, Error Reporter deduplication and the deploy generator. The in-memory test transport prevents SaaS requests.
10
+
11
+ Do not promote any Best effort combination solely because these focused smokes pass. SaaS receipts, ingestion/ledger/projection, authorized provider execution, Redis-backed worker lifecycle, dependency audit, load/benchmark gates and remote CI retain their own evidence requirements.
12
+
13
+ No tag or publication is authorized by preparation. After approval and green gates, only tag `v1.2.2`, matching Chronos::VERSION, triggers the protected Trusted Publishing workflow. Never replace a previously published artifact. The SaaS must select CLI recipes by explicitly supported release ranges per line and retain scripts for older clients.
14
+
15
+ ## Local execution evidence
16
+
17
+ The reviewed main-line integration implementations remain intact; the adapted port passed the following local Linux/amd64 gates:
18
+
19
+ - MRI 2.7.8, 3.0.7, 3.1.7, 3.2.11, 3.3.13 and 3.4.12: 227 RSpec examples (including modern integration and security regressions), RuboCop 1.57.2 with 241 files and no offenses, and 25 standalone tests with 127 assertions per runtime.
20
+ - Built artifact installation, HTTPS receipt/notification checks, safe integration generation, and an isolated Bundler frozen client verifying the loaded gem path. The HTTPS suite runs directly and through a Bundler-generated binstub.
21
+ - All eight Rails pairs in compatibility.md, now including automatic Sidekiq installation in the actual app: single initialization/request capture, SQL privacy, Active Job, genuine Error Reporter capture/deduplication and deploy generator namespace/idempotence/state.
22
+ - Standalone Sidekiq 7.3.10 configuration/middleware chains on all six Rubies. Actual failure re-raising is asserted; Redis/worker lifecycle is not exercised.
23
+ - Bundler Audit 0.9.1 on Ruby 3.3.13 core dependencies: no vulnerabilities found against ruby-advisory-db commit 92c25ce091b117bfe9ac54a5bbb0877356e9de7f (1290 advisories). This does not certify consumer/framework/stdlib graphs or EOL runtimes.
24
+ - Query-analysis benchmark (10000 iterations/1000 warmup), Rack comparison (10000 iterations/1000 warmup/5 samples), and fake-endpoint load/privacy (500 received of 500 events) passed. Measurements under emulation/concurrent tests are not production performance claims.
25
+ - Gem construction, SHA-256 calculation, SPDX SBOM (25 loaded build-environment packages), documentation/PR validation, workflow YAML and README shell syntax checks passed. The SBOM is build-environment evidence, not a complete inventory of native OS dependencies.
26
+
27
+ Local test containers are disposable; no customer or host Gemfile.lock was modified. Remote CI, provider/SaaS validation, Redis-backed lifecycle and final approval remain open. No tag was created and no package was published.
data/exe/chronos ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require "chronos/cli"
3
+ exit Chronos::CLI.new.run(ARGV)
data/lib/chronos/agent.rb CHANGED
@@ -248,12 +248,17 @@ module Chronos
248
248
  end
249
249
 
250
250
  def context_for_capture(additional)
251
- merged = deep_merge(context_hash(@context_store.get), context_hash(additional))
251
+ current = context_hash(@context_store.get)
252
+ additions = context_hash(additional)
253
+ buffer = breadcrumb_buffer(additions) || breadcrumb_buffer(current)
254
+ copier = Core::BoundedCopy.new
255
+ merged = deep_merge(copier.call(current), copier.call(additions))
252
256
  merged.delete(:__chronos_captured_exceptions)
253
257
  merged.delete("__chronos_captured_exceptions")
254
258
  merged.delete(:__chronos_captured_events)
255
259
  merged.delete("__chronos_captured_events")
256
- buffer = merged.delete(:__chronos_breadcrumbs) || merged.delete("__chronos_breadcrumbs")
260
+ merged.delete(:__chronos_breadcrumbs)
261
+ merged.delete("__chronos_breadcrumbs")
257
262
  if buffer.respond_to?(:to_a)
258
263
  merged[:context] = context_hash(merged[:context]).merge("breadcrumbs" => buffer.to_a)
259
264
  end
@@ -262,9 +267,13 @@ module Chronos
262
267
  context_hash(additional)
263
268
  end
264
269
 
270
+ def breadcrumb_buffer(context)
271
+ context[:__chronos_breadcrumbs] || context["__chronos_breadcrumbs"]
272
+ end
273
+
265
274
  def telemetry_context(additional)
266
275
  merged = context_for_capture(additional)
267
- context = context_hash(merged.delete(:context) || merged.delete("context"))
276
+ context = context_hash(merged.delete(:context) || merged.delete("context")).dup
268
277
  parameters = merged.delete(:parameters) || merged.delete("parameters")
269
278
  user = merged.delete(:user) || merged.delete("user")
270
279
  context["parameters"] = parameters if parameters.is_a?(Hash) && !parameters.empty?
@@ -272,9 +281,11 @@ module Chronos
272
281
  context.merge(merged)
273
282
  end
274
283
 
275
- def deep_merge(left, right)
284
+ def deep_merge(left, right, depth = 0)
285
+ return {} if depth >= Core::BoundedCopy::MAX_DEPTH
286
+
276
287
  left.merge(right) do |_key, old_value, new_value|
277
- old_value.is_a?(Hash) && new_value.is_a?(Hash) ? deep_merge(old_value, new_value) : new_value
288
+ old_value.is_a?(Hash) && new_value.is_a?(Hash) ? deep_merge(old_value, new_value, depth + 1) : new_value
278
289
  end
279
290
  end
280
291
 
@@ -0,0 +1,117 @@
1
+ require "optparse"
2
+ require "json"
3
+ require "chronos/version"
4
+ require "chronos/deploy/configuration"
5
+ require "chronos/deploy/installer"
6
+
7
+ module Chronos
8
+ # @responsibility Parse explicit deployment commands and return sanitized operational results.
9
+ # Local command parser; operational responses use a versioned, allowlisted JSON envelope.
10
+ class CLI
11
+ HELP = "chronos deploy notify|verify|diagnose --offline|install --provider NAME\n" \
12
+ "Options: --root PATH, --project-id ID, --environment NAME, --service-name NAME,\n" \
13
+ "--deploy-id ID, --revision SHA, --version VERSION, --host URL, --timeout SECONDS,\n" \
14
+ "--json, --on-error=warn, --dry-run, --check, --force\n" \
15
+ "Project key: CHRONOS_PROJECT_KEY only. No application boot.\n" \
16
+ "Exit: 0 confirmed/local success; 1 invalid configuration; 2 unconfirmed delivery; 3 internal failure.\n" \
17
+ "warn returns 0 for unconfirmed delivery, retaining success=false. Never retry blindly.\n".freeze
18
+
19
+ def initialize(output = $stdout, env = ENV)
20
+ @output = output
21
+ @env = env
22
+ end
23
+
24
+ def run(arguments)
25
+ args = arguments.dup
26
+ return local(HELP) if help?(args)
27
+ return local(Chronos::VERSION) if args == ["--version"]
28
+ raise ArgumentError unless args.shift == "deploy"
29
+
30
+ command = args.shift
31
+ raise ArgumentError unless %w(notify verify diagnose install).include?(command)
32
+
33
+ options = parse_options(args)
34
+ return local(HELP) if options[:help]
35
+ raise ArgumentError unless args.empty? && options[:timeout] > 0 && options[:timeout] <= 60
36
+
37
+ validate_options!(command, options)
38
+ execute(command, options)
39
+ rescue Deploy::Error => e
40
+ result(false, "invalid_configuration_or_usage", 1, e.details.merge(:message => e.message))
41
+ rescue OptionParser::ParseError, ArgumentError, Psych::Exception
42
+ result(false, "invalid_configuration_or_usage", 1,
43
+ :message => "Invalid options or public configuration; see chronos --help.")
44
+ rescue StandardError
45
+ result(false, "internal_failure", 3)
46
+ end
47
+
48
+ private
49
+
50
+ def help?(args)
51
+ args.empty? || %w(help --help -h).include?(args.first)
52
+ end
53
+
54
+ def parse_options(args)
55
+ options = {:timeout => 5.0}
56
+ parser = OptionParser.new
57
+ %w(root project-id environment service-name deploy-id revision version host provider).each do |name|
58
+ parser.on("--#{name} VALUE") { |value| options[name.tr("-", "_").to_sym] = value }
59
+ end
60
+ %w(json offline dry-run check force).each do |name|
61
+ parser.on("--#{name}") { options[name.tr("-", "_").to_sym] = true }
62
+ end
63
+ parser.on("--timeout SECONDS", Float) { |value| options[:timeout] = value }
64
+ parser.on("--on-error POLICY", %w(warn fail)) { |value| options[:on_error] = value }
65
+ parser.on("--help") { options[:help] = true }
66
+ parser.parse!(args)
67
+ options
68
+ end
69
+
70
+ def execute(command, options)
71
+ return result(true, Deploy::Installer.new.install(options), 0) if command == "install"
72
+
73
+ values = Deploy::Configuration.read(options, @env)
74
+ if command == "diagnose"
75
+ raise Deploy::Error, "Diagnose requires --offline." unless options[:offline]
76
+
77
+ Deploy::Configuration.validate!(values, "notify")
78
+ return result(true, "local_configuration_only", 0,
79
+ :message => "Local configuration is complete; credentials and delivery were not authenticated.")
80
+ end
81
+ deliver(command, values, options)
82
+ end
83
+
84
+ def deliver(command, values, options)
85
+ require "chronos/deploy/runner"
86
+ delivery = Deploy::Runner.new.call(command, values, options[:timeout])
87
+ success = command == "verify" ? delivery.success? : delivery == true
88
+ details = command == "verify" ? {:verification_status => delivery.status} : {}
89
+ code = success || options[:on_error] == "warn" ? 0 : 2
90
+ result(success, success ? "confirmed" : "unconfirmed", code, details)
91
+ end
92
+
93
+ def validate_options!(command, options)
94
+ common = [:root, :json, :help]
95
+ operational = [:project_id, :environment, :service_name, :deploy_id, :revision, :version, :host, :timeout]
96
+ allowed = command == "install" ? common + [:provider, :dry_run, :check, :force, :timeout] : common + operational
97
+ allowed += [:offline] if command == "diagnose"
98
+ allowed += [:on_error] if %w(notify verify).include?(command)
99
+ unless (options.keys - allowed).empty?
100
+ raise Deploy::Error, "Option does not apply to this command; see chronos --help."
101
+ end
102
+ raise Deploy::Error, "Use --check without --force." if options[:force] && options[:check]
103
+ end
104
+
105
+ def local(message)
106
+ @output.puts(message)
107
+ 0
108
+ end
109
+
110
+ def result(success, state, code, details = {})
111
+ data = {:schema_version => 1, :success => success, :state => state}.merge(details)
112
+ # JSON is the default operational format; --json makes that intent explicit.
113
+ @output.puts(JSON.generate(data))
114
+ code
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,69 @@
1
+ module Chronos
2
+ module Core
3
+ # @responsibility Copy untrusted diagnostic trees without cycles, unbounded recursion or caller mutation.
4
+ # Ruby 2.2-compatible structural budgets apply before immutable event construction.
5
+ class BoundedCopy
6
+ MAX_DEPTH = 10
7
+ MAX_ITEMS = 100
8
+ MAX_NODES = 2_000
9
+ MAX_STRING_BYTES = 8192
10
+
11
+ def call(value)
12
+ copy(value, 0, :nodes => 0, :seen => {}.compare_by_identity)
13
+ end
14
+
15
+ private
16
+
17
+ def copy(value, depth, state)
18
+ state[:nodes] += 1
19
+ return "<node limit reached>".freeze if state[:nodes] > MAX_NODES
20
+ return "<maximum depth reached>".freeze if depth >= MAX_DEPTH
21
+ return container(value, depth, state) if value.is_a?(Hash) || value.is_a?(Array)
22
+
23
+ scalar(value)
24
+ end
25
+
26
+ def container(value, depth, state)
27
+ return "<circular reference>".freeze if state[:seen][value]
28
+
29
+ state[:seen][value] = true
30
+ result = value.is_a?(Hash) ? copy_hash(value, depth, state) : copy_array(value, depth, state)
31
+ state[:seen].delete(value)
32
+ result.freeze
33
+ end
34
+
35
+ def copy_hash(value, depth, state)
36
+ result = {}
37
+ value.each do |key, child|
38
+ break if result.length >= MAX_ITEMS || state[:nodes] >= MAX_NODES
39
+
40
+ result[copy(key, depth + 1, state)] = copy(child, depth + 1, state)
41
+ end
42
+ result
43
+ end
44
+
45
+ def copy_array(value, depth, state)
46
+ result = []
47
+ value.each do |child|
48
+ break if result.length >= MAX_ITEMS || state[:nodes] >= MAX_NODES
49
+
50
+ result << copy(child, depth + 1, state)
51
+ end
52
+ result
53
+ end
54
+
55
+ def scalar(value)
56
+ case value
57
+ when String
58
+ return "<string limit reached>".freeze if value.bytesize > MAX_STRING_BYTES
59
+
60
+ value.dup.freeze
61
+ when NilClass, TrueClass, FalseClass, Symbol, Numeric
62
+ value
63
+ else
64
+ "<#{value.class.name}>".freeze
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,3 +1,5 @@
1
+ require "chronos/core/bounded_copy"
2
+
1
3
  module Chronos
2
4
  module Core
3
5
  # Immutable value object containing a normalized Ruby exception.
@@ -22,8 +24,9 @@ module Chronos
22
24
  attr_reader(*ATTRIBUTES)
23
25
 
24
26
  def initialize(attributes)
27
+ copied = BoundedCopy.new.call(attributes)
25
28
  ATTRIBUTES.each do |attribute|
26
- instance_variable_set("@#{attribute}", immutable_copy(attributes[attribute]))
29
+ instance_variable_set("@#{attribute}", copied[attribute])
27
30
  end
28
31
  freeze
29
32
  end
@@ -33,33 +36,6 @@ module Chronos
33
36
  result[attribute] = public_send(attribute)
34
37
  end
35
38
  end
36
-
37
- private
38
-
39
- def immutable_copy(value)
40
- case value
41
- when Hash
42
- value.each_with_object({}) do |(key, child), copy|
43
- copy[immutable_copy(key)] = immutable_copy(child)
44
- end
45
- when Array
46
- value.map { |child| immutable_copy(child) }
47
- when String
48
- value.dup
49
- when NilClass, TrueClass, FalseClass, Symbol, Numeric
50
- value
51
- else
52
- value.dup
53
- end.freeze
54
- rescue TypeError
55
- value
56
- rescue StandardError
57
- begin
58
- value.dup.freeze
59
- rescue StandardError
60
- value
61
- end
62
- end
63
39
  end
64
40
  end
65
41
  end
@@ -53,7 +53,7 @@ module Chronos
53
53
  def sanitize_type(value, depth, state)
54
54
  return sanitize_hash(value, depth, state) if value.is_a?(Hash)
55
55
  return sanitize_array(value, depth, state) if value.is_a?(Array)
56
- return @sensitive_value_filter.call(value) if value.is_a?(String)
56
+ return @sensitive_value_filter.call(value.to_s) if value.is_a?(String) || value.is_a?(Symbol)
57
57
 
58
58
  value
59
59
  end