chronos-ruby 1.2.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/README.md +158 -106
- data/docs/adr/legacy-deploy-cli.md +21 -0
- data/docs/adr/transitional-deploy-cli.md +11 -0
- data/docs/compatibility.md +31 -18
- data/docs/deploy-cli.md +35 -0
- data/docs/legacy-test-environment.md +59 -0
- data/docs/modules/rails-legacy.md +2 -2
- data/docs/modules/sidekiq-legacy.md +7 -10
- data/docs/release-1.1.4-readiness.md +44 -0
- data/docs/release-1.2.2-readiness.md +27 -0
- data/exe/chronos +3 -0
- data/lib/chronos/agent.rb +16 -5
- data/lib/chronos/cli.rb +117 -0
- data/lib/chronos/core/bounded_copy.rb +69 -0
- data/lib/chronos/core/notice.rb +4 -28
- data/lib/chronos/core/sanitizer.rb +1 -1
- data/lib/chronos/core/sql_lexer.rb +73 -0
- data/lib/chronos/core/sql_normalizer.rb +7 -7
- data/lib/chronos/core/telemetry_event.rb +5 -18
- data/lib/chronos/deploy/configuration.rb +96 -0
- data/lib/chronos/deploy/error.rb +14 -0
- data/lib/chronos/deploy/installer.rb +65 -0
- data/lib/chronos/deploy/providers.rb +50 -0
- data/lib/chronos/deploy/runner.rb +44 -0
- data/lib/chronos/deploy/secure_directory.rb +125 -0
- data/lib/chronos/rails/active_record_query_inspector.rb +2 -4
- data/lib/chronos/rails/installer.rb +1 -1
- data/lib/chronos/rails/railtie.rb +1 -1
- data/lib/chronos/rails.rb +3 -3
- data/lib/chronos/rake_tasks.rb +13 -1
- data/lib/chronos/version.rb +1 -1
- data/lib/generators/chronos/deploy/install/install_generator.rb +26 -0
- metadata +47 -6
data/docs/compatibility.md
CHANGED
|
@@ -1,37 +1,50 @@
|
|
|
1
1
|
# Compatibility
|
|
2
2
|
|
|
3
|
-
Chronos Ruby 1.2.
|
|
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
|
|
8
|
-
|
|
9
|
-
| 2.7–3.2
|
|
10
|
-
| 3.3–3.4
|
|
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
|
|
16
|
-
|
|
17
|
-
| 2.7–3.2
|
|
18
|
-
| 3.1–3.4
|
|
19
|
-
| Rails 4–6 | —
|
|
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
|
|
24
|
-
|
|
25
|
-
| 2.7–3.4
|
|
26
|
-
| Sidekiq 4–6 | —
|
|
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;
|
|
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
|
|
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.
|
data/docs/deploy-cli.md
ADDED
|
@@ -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
|
|
1
|
+
# Rails 7 transitional integration
|
|
2
2
|
|
|
3
|
-
Version 0.5
|
|
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
|
|
1
|
+
# Sidekiq 7 transitional integration
|
|
2
2
|
|
|
3
|
-
|
|
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", "~>
|
|
7
|
-
gem "chronos-ruby", "~> 1.
|
|
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
|
|
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
|
-
|
|
18
|
+
## Validation scope
|
|
19
19
|
|
|
20
|
-
|
|
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
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
|
-
|
|
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
|
-
|
|
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
|
|
data/lib/chronos/cli.rb
ADDED
|
@@ -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
|
data/lib/chronos/core/notice.rb
CHANGED
|
@@ -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}",
|
|
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
|