hecks 2.7.0 → 2.9.0
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/exe/hecks +11 -0
- data/lib/hecks/adapters/driven/claude_code.rb +4 -104
- data/lib/hecks/adapters/driven/d1.rb +44 -237
- data/lib/hecks/adapters/driven/folder.rb +17 -76
- data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
- data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
- data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
- data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
- data/lib/hecks/adapters/driven/heki.rb +13 -42
- data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
- data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
- data/lib/hecks/adapters/driven/lambda.rb +10 -84
- data/lib/hecks/adapters/driven/local_storage.rb +5 -40
- data/lib/hecks/adapters/driven/memory.rb +18 -99
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
- data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
- data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
- data/lib/hecks/adapters/driven/postgres.rb +145 -134
- data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
- data/lib/hecks/adapters/driven/prism.rb +7 -30
- data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
- data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
- data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
- data/lib/hecks/adapters/driven/sqlite.rb +128 -65
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +15 -37
- data/lib/hecks/adapters/driven.rb +5 -36
- data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
- data/lib/hecks/behaviors/dsl.rb +7 -25
- data/lib/hecks/behaviors/expectations.rb +32 -142
- data/lib/hecks/behaviors/ir.rb +7 -20
- data/lib/hecks/behaviors/rspec.rb +6 -17
- data/lib/hecks/behaviors/runner.rb +10 -34
- data/lib/hecks/behaviors.rb +4 -8
- data/lib/hecks/bluebook/aggregate.rb +9 -75
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
- data/lib/hecks/bluebook/assembly/build.rb +4 -15
- data/lib/hecks/bluebook/assembly/contract.rb +15 -136
- data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
- data/lib/hecks/bluebook/assembly/marks.rb +31 -223
- data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
- data/lib/hecks/bluebook/assembly.rb +6 -45
- data/lib/hecks/bluebook/attribute.rb +12 -61
- data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
- data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
- data/lib/hecks/bluebook/behaviour/chapter.rb +17 -42
- data/lib/hecks/bluebook/behaviour/command.rb +14 -66
- data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
- data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
- data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
- data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
- data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
- data/lib/hecks/bluebook/behaviour/read_model.rb +38 -26
- data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
- data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
- data/lib/hecks/bluebook/capabilities.rb +17 -49
- data/lib/hecks/bluebook/chapter.rb +10 -48
- data/lib/hecks/bluebook/command.rb +23 -81
- data/lib/hecks/bluebook/domain_port.rb +14 -63
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +17 -65
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
- data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
- data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -189
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
- data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
- data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
- data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
- data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
- data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
- data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
- data/lib/hecks/bluebook/dsl/world_builder.rb +19 -106
- data/lib/hecks/bluebook/entity.rb +13 -71
- data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
- data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
- data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
- data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
- data/lib/hecks/bluebook/expression/resolver.rb +37 -660
- data/lib/hecks/bluebook/hexagon.rb +5 -28
- data/lib/hecks/bluebook/lifecycle.rb +5 -16
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
- data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
- data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
- data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
- data/lib/hecks/bluebook/meta_validator/shapes.rb +54 -268
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
- data/lib/hecks/bluebook/meta_validator.rb +79 -349
- data/lib/hecks/bluebook/model_check/client_profile.rb +10 -91
- data/lib/hecks/bluebook/model_check.rb +102 -600
- data/lib/hecks/bluebook/pattern_subset.rb +11 -69
- data/lib/hecks/bluebook/process_manager.rb +13 -83
- data/lib/hecks/bluebook/project_loader.rb +3 -39
- data/lib/hecks/bluebook/project_register.rb +12 -60
- data/lib/hecks/bluebook/query.rb +9 -48
- data/lib/hecks/bluebook/read_model.rb +57 -81
- data/lib/hecks/bluebook/reference.rb +7 -41
- data/lib/hecks/bluebook/smoke_test.rb +8 -98
- data/lib/hecks/bluebook/synthesizer.rb +13 -62
- data/lib/hecks/bluebook/translation.rb +26 -62
- data/lib/hecks/bluebook/value_object.rb +11 -36
- data/lib/hecks/bluebook.rb +5 -22
- data/lib/hecks/cache_dir.rb +84 -0
- data/lib/hecks/cli/document.rb +86 -0
- data/lib/hecks/cli/ir.rb +45 -0
- data/lib/hecks/cli/mcp.rb +31 -0
- data/lib/hecks/cli/mcp_door.rb +366 -0
- data/lib/hecks/cli/model_check.rb +203 -0
- data/lib/hecks/cli/project_cli.rb +90 -0
- data/lib/hecks/cli/project_diagrams.rb +36 -0
- data/lib/hecks/cli/run.rb +173 -0
- data/lib/hecks/cli/smoke_test.rb +45 -0
- data/lib/hecks/cli/stores.rb +53 -0
- data/lib/hecks/cli.rb +160 -0
- data/lib/hecks/construct.rb +8 -34
- data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
- data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
- data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
- data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
- data/lib/hecks/embryonaut_bluebook.rb +4 -71
- data/lib/hecks/facade/cli_door.rb +24 -106
- data/lib/hecks/facade/cli_runner.rb +28 -172
- data/lib/hecks/facade/command_request.rb +13 -33
- data/lib/hecks/facade/handle.rb +34 -121
- data/lib/hecks/facade/json_door.rb +31 -146
- data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
- data/lib/hecks/facade/surface/chapter.rb +10 -54
- data/lib/hecks/facade/surface.rb +8 -23
- data/lib/hecks/facade.rb +10 -25
- data/lib/hecks/forms/app.rb +15 -70
- data/lib/hecks/forms/command_form_renderer.rb +10 -29
- data/lib/hecks/forms/field_renderer.rb +17 -156
- data/lib/hecks/forms/field_shape.rb +30 -222
- data/lib/hecks/forms/html.rb +12 -47
- data/lib/hecks/forms/index_renderer.rb +1 -4
- data/lib/hecks/forms/page.rb +3 -9
- data/lib/hecks/forms/params.rb +14 -60
- data/lib/hecks/forms/port_argument.rb +2 -19
- data/lib/hecks/forms/query_form_renderer.rb +4 -79
- data/lib/hecks/forms/record_renderer.rb +8 -75
- data/lib/hecks/forms/record_table.rb +7 -50
- data/lib/hecks/forms/reference_options.rb +3 -30
- data/lib/hecks/forms/value_object_shape.rb +8 -35
- data/lib/hecks/forms.rb +5 -33
- data/lib/hecks/fqn.rb +5 -12
- data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
- data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
- data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
- data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
- data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
- data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
- data/lib/hecks/framework.rb +11 -80
- data/lib/hecks/freezer.rb +5 -39
- data/lib/hecks/grammar/expression.bluebook +15 -40
- data/lib/hecks/grammar/translation.bluebook +3 -6
- data/lib/hecks/grammar.rb +16 -41
- data/lib/hecks/ir.rb +14 -83
- data/lib/hecks/language/adapter.bluebook +2 -7
- data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
- data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
- data/lib/hecks/language/bluebook/command.bluebook +27 -144
- data/lib/hecks/language/bluebook/entity.bluebook +24 -97
- data/lib/hecks/language/bluebook/policy.bluebook +16 -67
- data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
- data/lib/hecks/language/bluebook/projection.bluebook +126 -60
- data/lib/hecks/language/bluebook/query.bluebook +12 -43
- data/lib/hecks/language/bluebook/shape.bluebook +23 -73
- data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
- data/lib/hecks/language/bluebook/vocabulary.bluebook +98 -197
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
- data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
- data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
- data/lib/hecks/language/oidc.json +35 -0
- data/lib/hecks/language/port.bluebook +4 -17
- data/lib/hecks/language/translation/translation.bluebook +3 -9
- data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
- data/lib/hecks/language/world/wiring.bluebook +1 -3
- data/lib/hecks/language/world/world.bluebook +5 -7
- data/lib/hecks/literal.rb +8 -50
- data/lib/hecks/mcp_door_scope.rb +95 -0
- data/lib/hecks/mcp_stdio_guard.rb +4 -41
- data/lib/hecks/naming.rb +29 -137
- data/lib/hecks/ports/access_control.rb +7 -23
- data/lib/hecks/ports/agent/answers.rb +5 -23
- data/lib/hecks/ports/agent.rb +47 -172
- data/lib/hecks/ports/authentication.rb +12 -39
- data/lib/hecks/ports/authorization.rb +16 -43
- data/lib/hecks/ports/clock.rb +5 -45
- data/lib/hecks/ports/extraction.rb +7 -12
- data/lib/hecks/ports/identity_assignment.rb +8 -29
- data/lib/hecks/ports/identity_generation.rb +6 -29
- data/lib/hecks/ports/identity_resolution.rb +2 -10
- data/lib/hecks/ports/key_vault.rb +9 -45
- data/lib/hecks/ports/persistence/append_only.rb +62 -65
- data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
- data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
- data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
- data/lib/hecks/ports/persistence/plugin.rb +13 -42
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +7 -26
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +22 -122
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
- data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
- data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
- data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
- data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
- data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
- data/lib/hecks/ports/persistence/state_codec.rb +23 -103
- data/lib/hecks/ports/persistence.rb +7 -14
- data/lib/hecks/ports/projection.rb +42 -84
- data/lib/hecks/ports/query/in_memory.rb +10 -58
- data/lib/hecks/ports/query/ordering.rb +11 -32
- data/lib/hecks/ports/query.rb +14 -37
- data/lib/hecks/ports.rb +2 -8
- data/lib/hecks/projections/bootstrap_table.rb +11 -43
- data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
- data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
- data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
- data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
- data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
- data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
- data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
- data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
- data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
- data/lib/hecks/projections/deploy/fargate.rb +35 -202
- data/lib/hecks/projections/deploy/lambda.rb +151 -679
- data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
- data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
- data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
- data/lib/hecks/projections/deploy/preview/script.rb +2 -14
- data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
- data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
- data/lib/hecks/projections/deploy/preview/template.rb +2 -58
- data/lib/hecks/projections/deploy/preview.rb +2 -67
- data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
- data/lib/hecks/projections/deploy/scripts.rb +4 -57
- data/lib/hecks/projections/deploy/shared.rb +47 -146
- data/lib/hecks/projections/deploy/smoke.rb +4 -61
- data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
- data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
- data/lib/hecks/projections/deploy/template_diff.rb +5 -57
- data/lib/hecks/projections/diagrams.rb +20 -582
- data/lib/hecks/projections/glossary/html.rb +14 -130
- data/lib/hecks/projections/glossary/markdown.rb +8 -69
- data/lib/hecks/projections/glossary/mermaid.rb +9 -66
- data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
- data/lib/hecks/projections/glossary/sentences.rb +14 -159
- data/lib/hecks/projections/glossary.rb +30 -222
- data/lib/hecks/projections/ir.rb +3 -10
- data/lib/hecks/projections/model/deviations.rb +15 -42
- data/lib/hecks/projections/model.rb +12 -51
- data/lib/hecks/projections/oidc.rb +12 -56
- data/lib/hecks/projections/parser_table.rb +9 -28
- data/lib/hecks/projections/reference.rb +9 -16
- data/lib/hecks/projections/rust_vocabulary.rb +12 -250
- data/lib/hecks/projections/shape.rb +3 -29
- data/lib/hecks/projections/statements.rb +7 -86
- data/lib/hecks/projections/vocabulary.rb +6 -22
- data/lib/hecks/projections.rb +2 -16
- data/lib/hecks/projector/cli_projector.rb +60 -375
- data/lib/hecks/projector/docs_projector.rb +10 -201
- data/lib/hecks/projector/exporter.rb +46 -138
- data/lib/hecks/projector/ir_projector.rb +2 -9
- data/lib/hecks/projector/narrate_projector.rb +15 -189
- data/lib/hecks/projector/target.rb +13 -76
- data/lib/hecks/projector.rb +30 -121
- data/lib/hecks/query_specification/common/comparators.rb +5 -26
- data/lib/hecks/query_specification/common/comparison.rb +35 -143
- data/lib/hecks/query_specification/common/dsl.rb +8 -24
- data/lib/hecks/query_specification/common/null_policy.rb +20 -70
- data/lib/hecks/query_specification/common/options.rb +2 -6
- data/lib/hecks/query_specification/common/where_clause.rb +2 -10
- data/lib/hecks/query_specification/field_path.rb +37 -77
- data/lib/hecks/query_specification/hop_path.rb +12 -76
- data/lib/hecks/release/gem_pin.rb +2 -26
- data/lib/hecks/release/runner/ci_publisher.rb +93 -0
- data/lib/hecks/release/runner/commands.rb +66 -0
- data/lib/hecks/release/runner/console.rb +52 -0
- data/lib/hecks/release/runner/gem_publisher.rb +43 -0
- data/lib/hecks/release/runner/git.rb +48 -0
- data/lib/hecks/release/runner/npm_publisher.rb +75 -0
- data/lib/hecks/release/runner/preflight.rb +104 -0
- data/lib/hecks/release/runner/published.rb +55 -0
- data/lib/hecks/release/runner/tagger.rb +80 -0
- data/lib/hecks/release/runner.rb +215 -0
- data/lib/hecks/rendering.rb +4 -26
- data/lib/hecks/router/namespace_installer.rb +12 -36
- data/lib/hecks/router.rb +4 -8
- data/lib/hecks/runtime/aggregate_lock.rb +8 -33
- data/lib/hecks/runtime/boot_gates.rb +3 -18
- data/lib/hecks/runtime/caller.rb +19 -59
- data/lib/hecks/runtime/capability_graph.rb +5 -21
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
- data/lib/hecks/runtime/command_interpreter.rb +125 -307
- data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
- data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
- data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
- data/lib/hecks/runtime/command_rules/emission.rb +8 -21
- data/lib/hecks/runtime/command_rules/references.rb +23 -202
- data/lib/hecks/runtime/dependency_planning.rb +30 -102
- data/lib/hecks/runtime/dispatcher.rb +82 -241
- data/lib/hecks/runtime/entity_element.rb +40 -462
- data/lib/hecks/runtime/entity_interpreter.rb +65 -220
- data/lib/hecks/runtime/errors.rb +32 -115
- data/lib/hecks/runtime/event.rb +3 -25
- data/lib/hecks/runtime/identity.rb +18 -154
- data/lib/hecks/runtime/instance.rb +18 -150
- data/lib/hecks/runtime/interpreting.rb +11 -70
- data/lib/hecks/runtime/invocation.rb +28 -164
- data/lib/hecks/runtime/loader.rb +48 -186
- data/lib/hecks/runtime/outbox.rb +38 -261
- data/lib/hecks/runtime/policy_interpreter.rb +38 -200
- data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
- data/lib/hecks/runtime/query_interpreter.rb +45 -187
- data/lib/hecks/runtime/reaction_invocation.rb +36 -138
- data/lib/hecks/runtime/read_model_interpreter.rb +139 -208
- data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
- data/lib/hecks/runtime/reference_hop.rb +8 -81
- data/lib/hecks/runtime/refusal_wording.rb +8 -89
- data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
- data/lib/hecks/runtime/registry/verification.rb +52 -225
- data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
- data/lib/hecks/runtime/registry.rb +103 -244
- data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
- data/lib/hecks/runtime/routing.rb +4 -15
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
- data/lib/hecks/runtime/saga_interpreter.rb +79 -263
- data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
- data/lib/hecks/runtime/tenant_check.rb +6 -68
- data/lib/hecks/runtime/tenant_scope.rb +3 -28
- data/lib/hecks/runtime/value/admission.rb +16 -67
- data/lib/hecks/runtime/value/coercion.rb +155 -641
- data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
- data/lib/hecks/runtime/value.rb +12 -84
- data/lib/hecks/runtime.rb +17 -65
- data/lib/hecks/storehouse.rb +146 -567
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
- data/lib/hecks/vendoring/git_environment.rb +2 -8
- data/lib/hecks/vendoring/git_source.rb +4 -10
- data/lib/hecks/vendoring.rb +7 -46
- data/lib/hecks/version.rb +3 -14
- data/lib/hecks/vocabulary.rb +5 -0
- data/lib/hecks.rb +32 -82
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
- metadata +30 -59
- data/lib/hecks/bench/cli.rb +0 -74
- data/lib/hecks/bench/environment.rb +0 -100
- data/lib/hecks/bench/postgres_probe.rb +0 -41
- data/lib/hecks/bench/report.rb +0 -116
- data/lib/hecks/bench/ruby_runner.rb +0 -151
- data/lib/hecks/bench/run.rb +0 -55
- data/lib/hecks/bench/rust_runner.rb +0 -165
- data/lib/hecks/bench/stats.rb +0 -75
- data/lib/hecks/bench/suite.rb +0 -172
- data/lib/hecks/bench/workload.rb +0 -137
- data/lib/hecks/bench.rb +0 -28
- data/lib/hecks/codemod.rb +0 -443
- data/lib/hecks/corpus.rb +0 -479
- data/lib/hecks/doc/reference.rb +0 -585
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
- data/lib/hecks/fuzzing/combination_miner.rb +0 -178
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
- data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
- data/lib/hecks/fuzzing/differential.rb +0 -192
- data/lib/hecks/fuzzing/domain_generator.rb +0 -871
- data/lib/hecks/fuzzing/era_boundary.rb +0 -154
- data/lib/hecks/fuzzing/form_census.rb +0 -285
- data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
- data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
- data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
- data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
- data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
- data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
- data/lib/hecks/fuzzing/properties/guards.rb +0 -398
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
- data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
- data/lib/hecks/fuzzing/properties/querying.rb +0 -260
- data/lib/hecks/fuzzing/properties.rb +0 -267
- data/lib/hecks/fuzzing/qa_settings.rb +0 -164
- data/lib/hecks/fuzzing/replay.rb +0 -930
- data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
- data/lib/hecks/fuzzing/self_consistency.rb +0 -825
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
- data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
- data/lib/hecks/fuzzing/shrinker.rb +0 -309
- data/lib/hecks/fuzzing/structural_skips.rb +0 -53
- data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
- data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
- data/lib/hecks/fuzzing/value_generator.rb +0 -340
- data/lib/hecks/fuzzing.rb +0 -23
- data/lib/hecks/grammar/evolve.rb +0 -524
- data/lib/hecks/query_ir.rb +0 -482
|
@@ -2,13 +2,8 @@ require_relative "../../vocabulary"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Adapters
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# files off disk, in the order `Vocabulary.fetch("LoadOrder")` requires,
|
|
8
|
-
# and locate a domain's own root directory by walking up from any file
|
|
9
|
-
# inside it looking for a `.hecksagon`. What `Hecks.boot(path)` uses when
|
|
10
|
-
# a caller names a directory rather than an explicit file list (see
|
|
11
|
-
# `Loader.boot_files` for that other path).
|
|
5
|
+
# Loads a domain's files off disk in `Vocabulary.fetch("LoadOrder")` order and finds
|
|
6
|
+
# a domain's root directory. Used by `Hecks.boot(path)` when given a directory.
|
|
12
7
|
class Folder
|
|
13
8
|
DOMAIN_ORDER = Hecks::Vocabulary.fetch("LoadOrder")
|
|
14
9
|
PORTS = "ports".freeze
|
|
@@ -41,34 +36,12 @@ module Hecks
|
|
|
41
36
|
load_each(File.join(root, ADAPTERS), %w[*.adapter */*.adapter */*/*.adapter])
|
|
42
37
|
end
|
|
43
38
|
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
# refuses references that are perfectly well declared a file later —
|
|
48
|
-
# see MetaValidator.defer for the whole reasoning. DOMAIN_ORDER
|
|
49
|
-
# already places every `*.bluebook` ahead of hecksagons and worlds,
|
|
50
|
-
# so the window ends at the last chapter pattern rather than at a
|
|
51
|
-
# hand-written list this would otherwise have to keep in step.
|
|
39
|
+
# Loads bluebook chapters first, judged once as a deferred group, then everything
|
|
40
|
+
# else in `DOMAIN_ORDER`. Deferring keeps a chapter split across files from being
|
|
41
|
+
# judged before its later files exist (see MetaValidator.defer).
|
|
52
42
|
#
|
|
53
|
-
# `environment:`
|
|
54
|
-
#
|
|
55
|
-
# the provenance. A caller passing `Hecks.boot(path, environment:
|
|
56
|
-
# "production")` gets exactly `environments/production.hecksagon`
|
|
57
|
-
# and `environments/production.world` loaded, whichever exist (a
|
|
58
|
-
# missing one is a silent no-op — not every environment overrides
|
|
59
|
-
# both; see docs/implemented/guides/wiring.md's "Swapping wiring per
|
|
60
|
-
# environment" for the motivating case: swapping a driven adapter
|
|
61
|
-
# — e.g. a real payment gateway for a mock one, or a hosting
|
|
62
|
-
# layer's tenancy settings — without an `if`/`else` anywhere in
|
|
63
|
-
# the domain's own wiring). Never matched by `DOMAIN_ORDER`'s own
|
|
64
|
-
# globs (all non-recursive, none named `environments/*`), so this
|
|
65
|
-
# is the only thing that ever reaches them. Loaded as genuine
|
|
66
|
-
# `Hecks.hecksagon "SameDomain" do ... end` / `Hecks.world
|
|
67
|
-
# "SameDomain" do ... end` blocks — merged into the base file's
|
|
68
|
-
# own hecksagon/world (Registry#add_hecksagon / #add_world,
|
|
69
|
-
# concatenate/override rather than replace), so an overlay can
|
|
70
|
-
# rebind or add settings for anything the base file declared
|
|
71
|
-
# without needing to know what else the base file said.
|
|
43
|
+
# An `environment:` overlay (`environments/<name>.hecksagon` and `.world`) loads
|
|
44
|
+
# last and merges into the base hecksagon/world; a missing overlay file is skipped.
|
|
72
45
|
#
|
|
73
46
|
# @param directory [String] the domain's bluebook directory to load, as
|
|
74
47
|
# `bluebook_directory` resolves it
|
|
@@ -90,12 +63,8 @@ module Hecks
|
|
|
90
63
|
load_each(directory, [File.join("environments", "#{environment}.world")])
|
|
91
64
|
end
|
|
92
65
|
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
# unit callers load. Builders group declarations by the chapter name in
|
|
96
|
-
# each file, so a folder may hold more than one chapter without a catalog.
|
|
97
|
-
# Sorting makes source order deterministic while the deferred window keeps
|
|
98
|
-
# cross-file references from being judged against a partial chapter.
|
|
66
|
+
# Loads every bluebook in a folder as one declaration set, judged after all are loaded
|
|
67
|
+
# so cross-file references never meet a partial chapter.
|
|
99
68
|
#
|
|
100
69
|
# @param directory [String] the directory to glob for bluebook chapter files
|
|
101
70
|
# @param patterns [Array<String>] glob patterns, relative to `directory`, selecting the
|
|
@@ -106,23 +75,11 @@ module Hecks
|
|
|
106
75
|
Bluebook::MetaValidator.judge_deferred!(Hecks.current_registry)
|
|
107
76
|
end
|
|
108
77
|
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
# a `.behaviors` file's `loads` scopes a boot this way specifically so a
|
|
114
|
-
# per-test boot never has to fake isolation by staging bluebooks into a
|
|
115
|
-
# tmpdir (see Loader.boot_files's own header for why that pattern is a
|
|
116
|
-
# hazard, not a convenience).
|
|
78
|
+
# Loads exactly the named files in place, without globbing (see `Loader.boot_files`).
|
|
79
|
+
#
|
|
80
|
+
# Files load by category, not in the caller's order: bluebooks (judged as one deferred
|
|
81
|
+
# group), then hecksagons, then worlds, since a hecksagon may reference bluebook constants.
|
|
117
82
|
#
|
|
118
|
-
# Ordered by category, not by the caller's own list order — same four
|
|
119
|
-
# groups `Vocabulary.fetch("LoadOrder")` walks a directory in
|
|
120
|
-
# (bluebook chapters, translations, hecksagons, worlds), because a
|
|
121
|
-
# hecksagon can reference a bluebook's own constants and must not load
|
|
122
|
-
# first regardless of which order a caller happened to write `loads
|
|
123
|
-
# "x.hecksagon", "x.bluebook"` in. Bluebook chapters are judged as one
|
|
124
|
-
# deferred group exactly like `load_domain` does, for the identical
|
|
125
|
-
# forward-reference reason (MetaValidator.defer's own header).
|
|
126
83
|
# @param files [Array<String>] file paths to load, relative to `bluebook_directory`
|
|
127
84
|
# @param environment [String, nil] the environment name whose overlay, if present,
|
|
128
85
|
# loads after the selected files
|
|
@@ -181,27 +138,11 @@ module Hecks
|
|
|
181
138
|
|
|
182
139
|
# Finds the nearest domain directory at or above `from`, or nil if there is not one.
|
|
183
140
|
#
|
|
184
|
-
#
|
|
185
|
-
#
|
|
186
|
-
# or nil if there is not one above you.
|
|
187
|
-
#
|
|
188
|
-
# Marked by a `.hecksagon`, not by a `.bluebook`. Chapters are
|
|
189
|
-
# everywhere: era translations, the language's own self-hosted grammar,
|
|
190
|
-
# and `spec/fixtures`, which holds a dozen unrelated ones in a single
|
|
191
|
-
# directory. A `.hecksagon` is the file that says "this is a domain, and
|
|
192
|
-
# here is how its aggregates are stored", which is exactly the claim a
|
|
193
|
-
# caller is relying on when they omit the path.
|
|
141
|
+
# Walks up from `from`, like git finding `.git`. A domain is marked by a `.hecksagon`,
|
|
142
|
+
# not a `.bluebook`, since chapters also appear in translations and fixtures.
|
|
194
143
|
#
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
# this corpus), or one holding the files directly.
|
|
198
|
-
# Normalised to the outer directory. Standing in `examples/banking/bluebook`,
|
|
199
|
-
# the `.hecksagon` is right there, so a plain walk stops on the
|
|
200
|
-
# `bluebook/` directory itself. Both boot identically — `bluebook_directory`
|
|
201
|
-
# accepts either and `Loader.boot` takes `File.dirname` of what it gets,
|
|
202
|
-
# so the registry root comes out the same — but `examples/banking` is the
|
|
203
|
-
# directory a person names, and the one a `.world`'s `dir "data"` reads
|
|
204
|
-
# as relative to.
|
|
144
|
+
# A `bluebook/` subdirectory result is normalised to its parent, the directory
|
|
145
|
+
# a `.world`'s `dir "data"` is relative to.
|
|
205
146
|
#
|
|
206
147
|
# @param from [String] the directory to walk up from; defaults to the process's current
|
|
207
148
|
# working directory
|
|
@@ -4,31 +4,14 @@ require_relative "../../ports/authentication"
|
|
|
4
4
|
|
|
5
5
|
module Hecks
|
|
6
6
|
module Adapters
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# (a console app's `google_auth.rb` did this on its own) into
|
|
10
|
-
# one adapter, so any hecks-based app gets Google sign-in for free, the
|
|
11
|
-
# same "one adapter, reusable everywhere" value every other adapter
|
|
12
|
-
# in this directory already has.
|
|
7
|
+
# Google's OIDC handshake — the `authentication` port's adapter for Google
|
|
8
|
+
# sign-in, via `oauth2` (code exchange) and `google-id-token` (verification).
|
|
13
9
|
#
|
|
14
|
-
# `oauth2`
|
|
15
|
-
#
|
|
16
|
-
# does only ID-token verification (signature checked against
|
|
17
|
-
# Google's real, rotating JWKS — real, maintained code, never
|
|
18
|
-
# hand-rolled here). Neither library decides what a verified
|
|
19
|
-
# (issuer, subject) means — that's `Ports::IdentityResolution`'s
|
|
20
|
-
# job, called by whoever consumes this port's `verify`.
|
|
10
|
+
# `oauth2`/`google-id-token` load lazily, inside the methods that use
|
|
11
|
+
# them, so a domain that never binds this adapter never needs them installed.
|
|
21
12
|
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# `authentication` to this adapter should never need these gems
|
|
25
|
-
# installed. This file loads in every boot (driven.rb's own
|
|
26
|
-
# unconditional require_relative list) ; the gems load only where
|
|
27
|
-
# a real handshake actually happens.
|
|
28
|
-
#
|
|
29
|
-
# `GOOGLE_CLIENT_ID`/`GOOGLE_CLIENT_SECRET`/`GOOGLE_REDIRECT_URI` —
|
|
30
|
-
# no defaults, on purpose: a login mechanism silently
|
|
31
|
-
# half-configured is worse than one that refuses to boot at all.
|
|
13
|
+
# `GOOGLE_CLIENT_ID`/`GOOGLE_CLIENT_SECRET`/`GOOGLE_REDIRECT_URI` have no
|
|
14
|
+
# defaults, on purpose: a half-configured login should refuse to boot.
|
|
32
15
|
module GoogleAuthentication
|
|
33
16
|
ISSUER = "https://accounts.google.com".freeze
|
|
34
17
|
|
|
@@ -48,9 +31,8 @@ module Hecks
|
|
|
48
31
|
)
|
|
49
32
|
end
|
|
50
33
|
|
|
51
|
-
# The URL to send a browser to, carrying a fresh CSRF `state` the
|
|
52
|
-
#
|
|
53
|
-
# checking again in `verify`.
|
|
34
|
+
# The URL to send a browser to, carrying a fresh CSRF `state` the caller
|
|
35
|
+
# must stash (typically in a session) and check again in `verify`.
|
|
54
36
|
#
|
|
55
37
|
# @return [Array(String, String)] the URL to send the browser to, and the fresh CSRF
|
|
56
38
|
# `state` embedded in it
|
|
@@ -66,38 +48,22 @@ module Hecks
|
|
|
66
48
|
[url, state]
|
|
67
49
|
end
|
|
68
50
|
|
|
69
|
-
# Exchanges `code` for tokens
|
|
70
|
-
#
|
|
71
|
-
# `state` matches what `authorization_url` handed out — a
|
|
72
|
-
# mismatch (or any failure in the exchange/verification itself)
|
|
73
|
-
# is a Ports::Authentication::ValidationError, never a
|
|
74
|
-
# silently-empty result. Returns
|
|
75
|
-
# {issuer:, subject:, email:, email_verified:} on success — never
|
|
76
|
-
# the raw token, never anything a caller would need to re-verify
|
|
77
|
-
# itself. `email_verified` rides along because a caller granting
|
|
78
|
-
# access off this email needs to know Google actually checked it.
|
|
79
|
-
#
|
|
51
|
+
# Exchanges `code` for tokens and verifies the ID token against Google's
|
|
52
|
+
# JWKS; `email_verified` tells a caller if Google actually checked the email.
|
|
80
53
|
# @param code [String] the authorization code the provider sent back
|
|
81
54
|
# @param state [String, nil] the state parameter the provider returned; nil is refused
|
|
82
55
|
# @param expected_state [String, nil] the state `authorization_url` handed out before
|
|
83
56
|
# the redirect; nil is refused
|
|
84
57
|
# @return [Hash{Symbol => Object}] `issuer:` and `subject:` (String), `email:` (String,
|
|
85
58
|
# or nil if the token carries none), `email_verified:` (Boolean)
|
|
86
|
-
# @raise [Ports::Authentication::ValidationError] if
|
|
87
|
-
#
|
|
88
|
-
# not verify
|
|
59
|
+
# @raise [Ports::Authentication::ValidationError] if the state check, code exchange, or
|
|
60
|
+
# ID token verification fails
|
|
89
61
|
# @raise [KeyError] if `GOOGLE_CLIENT_ID` or `GOOGLE_REDIRECT_URI` is unset, or a
|
|
90
62
|
# verified token lacks an `iss` or `sub` claim
|
|
91
63
|
def verify(code:, state:, expected_state:)
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
# ::
|
|
95
|
-
# at the moment an exception is being matched, not at parse
|
|
96
|
-
# time. An OAuth2::Error raised before a later `require
|
|
97
|
-
# "google-id-token"` line ran would make the rescue clause
|
|
98
|
-
# itself blow up on an undefined constant, masking the real
|
|
99
|
-
# error. Requiring both up front means whichever one raises
|
|
100
|
-
# first, the constant is already there to catch it.
|
|
64
|
+
# Both requires happen up front: the rescue below matches
|
|
65
|
+
# GoogleIDToken::ValidationError, resolved only when caught, so
|
|
66
|
+
# requiring it lazily after an OAuth2::Error would mask the real failure.
|
|
101
67
|
require "oauth2"
|
|
102
68
|
require "google-id-token"
|
|
103
69
|
|
|
@@ -2,45 +2,23 @@ require "time"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Adapters
|
|
5
|
-
#
|
|
6
|
-
# same
|
|
7
|
-
# already sitting in the store this adapter is handed, not a bridge
|
|
8
|
-
# to a second runtime. `Runtime::Dispatcher.new(registry)` is cheap
|
|
9
|
-
# to build fresh per call (`Registry#capability_graph`'s own
|
|
10
|
-
# neighbor, `#repository`, does the same kind of on-demand build) —
|
|
11
|
-
# nothing here holds one across calls, so there is no boot-order
|
|
12
|
-
# dependency on when Governance's bluebook loads relative to this
|
|
13
|
-
# adapter, only that it has by the time `holds_role?` is called.
|
|
14
|
-
#
|
|
15
|
-
# An active assignment, not merely a past one — `RoleAssignment`
|
|
16
|
-
# answers with every assignment an actor has ever held, current or
|
|
17
|
-
# past (`AssignmentsForActor`'s own description covers both), and
|
|
18
|
-
# leaves `ends_at` for the caller to read, the same deferral
|
|
19
|
-
# `Governance::RoleTransition.Allowed` makes for the same reason.
|
|
20
|
-
# This is that caller.
|
|
5
|
+
# The `authorization` port, answered by dispatching queries against Governance's records
|
|
6
|
+
# in the same registry. Only assignments with no `ends_at` count as live.
|
|
21
7
|
module GovernanceAuthorization
|
|
22
8
|
module_function
|
|
23
9
|
|
|
24
|
-
#
|
|
25
|
-
# `refuse_role_mismatch` already gives `actor_id` itself — an
|
|
26
|
-
# unbound `as_of` skips the `starts_at` check and an unbound
|
|
27
|
-
# `scope` skips the `scope` check, exactly the behavior before
|
|
28
|
-
# either existed. Neither is fetched here: `as_of` arrives already
|
|
29
|
-
# resolved from `Ports::Clock.now`, called by the caller at the
|
|
30
|
-
# door, never by this adapter — see `Ports::Clock`'s own header
|
|
31
|
-
# for why the dispatch path must not consult the clock itself.
|
|
10
|
+
# Reports whether `actor_id` holds a live grant of `role`.
|
|
32
11
|
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
# @param
|
|
37
|
-
# @param
|
|
38
|
-
#
|
|
12
|
+
# `as_of` and `scope` are opt-in: nil skips the matching check. `as_of` comes from the
|
|
13
|
+
# caller, since the dispatch path must not read the clock (see `Ports::Clock`).
|
|
14
|
+
#
|
|
15
|
+
# @param registry [Runtime::Registry] the booted registry
|
|
16
|
+
# @param actor_id [String] the actor whose grants are checked
|
|
17
|
+
# @param role [String, Symbol] the role name to look for
|
|
18
|
+
# @param as_of [Integer, nil] Unix epoch seconds; a later (or unparseable) `starts_at` fails
|
|
39
19
|
# @param scope [String, nil] the scope the caller acts in; nil skips the scope check
|
|
40
|
-
# @return [Boolean] true if
|
|
41
|
-
#
|
|
42
|
-
# @raise [Runtime::WiringError] if the loaded chapters providing `"authorization"` are
|
|
43
|
-
# not exactly one (see `provided_verb`)
|
|
20
|
+
# @return [Boolean] true if a live grant of `role` to `actor_id` passes both checks
|
|
21
|
+
# @raise [Runtime::WiringError] unless exactly one chapter provides `"authorization"`
|
|
44
22
|
def holds_role?(registry, actor_id:, role:, as_of: nil, scope: nil)
|
|
45
23
|
rows = Runtime::Dispatcher.new(registry).query(
|
|
46
24
|
provided_verb(registry, :assignments),
|
|
@@ -55,11 +33,7 @@ module Hecks
|
|
|
55
33
|
end
|
|
56
34
|
end
|
|
57
35
|
|
|
58
|
-
# `scope
|
|
59
|
-
# field here — a caller that never says which scope it is acting
|
|
60
|
-
# in gets the pre-scope behavior: any live assignment for the role
|
|
61
|
-
# authorizes, everywhere. A caller that does state one only
|
|
62
|
-
# authorizes against an assignment granted for that scope.
|
|
36
|
+
# Without a `scope`, any live assignment for the role authorizes everywhere.
|
|
63
37
|
#
|
|
64
38
|
# @param row [Hash{Symbol => Object}] one `RoleAssignment` row as
|
|
65
39
|
# `Runtime::Dispatcher#query` returns it; `:scope` holds a `{value: String}` Hash
|
|
@@ -69,13 +43,8 @@ module Hecks
|
|
|
69
43
|
scope.nil? || row[:scope][:value] == scope.to_s
|
|
70
44
|
end
|
|
71
45
|
|
|
72
|
-
# `starts_at` is
|
|
73
|
-
#
|
|
74
|
-
# here with `Time.parse` rather than compared lexically, since
|
|
75
|
-
# nothing guarantees every caller writes it zero-padded ISO 8601.
|
|
76
|
-
# Fails closed : a `starts_at` that does not parse is treated as
|
|
77
|
-
# not-yet-started rather than silently ignored, the same direction
|
|
78
|
-
# every other check in this method already fails.
|
|
46
|
+
# `starts_at` is free text, so it is parsed with `Time.parse` rather than compared
|
|
47
|
+
# lexically. Fails closed: an unparseable value counts as not yet started.
|
|
79
48
|
#
|
|
80
49
|
# @param row [Hash{Symbol => Object}] one `RoleAssignment` row as
|
|
81
50
|
# `Runtime::Dispatcher#query` returns it; `:starts_at` holds a `{value: String}` Hash
|
|
@@ -92,21 +61,13 @@ module Hecks
|
|
|
92
61
|
|
|
93
62
|
# Answers whether one role may act as another.
|
|
94
63
|
#
|
|
95
|
-
#
|
|
96
|
-
# is identified by the exact pair, so at most one row ever comes
|
|
97
|
-
# back ; still read as `.any?` rather than trusting that structurally,
|
|
98
|
-
# the same defensiveness `holds_role?` already has to have anyway
|
|
99
|
-
# since `AssignmentsForActor` can return several.
|
|
64
|
+
# Read with `.any?` rather than trusting that the exact-pair lookup returns one row.
|
|
100
65
|
#
|
|
101
|
-
# @param registry [Runtime::Registry] the booted registry
|
|
102
|
-
# provider's verb
|
|
66
|
+
# @param registry [Runtime::Registry] the booted registry
|
|
103
67
|
# @param from_role [String, Symbol] the role the caller holds, compared as a String
|
|
104
|
-
# @param to_role [String, Symbol] the role the caller wants to act as, compared as a
|
|
105
|
-
#
|
|
106
|
-
# @
|
|
107
|
-
# `to_role`
|
|
108
|
-
# @raise [Runtime::WiringError] if the loaded chapters providing `"authorization"` are
|
|
109
|
-
# not exactly one (see `provided_verb`)
|
|
68
|
+
# @param to_role [String, Symbol] the role the caller wants to act as, compared as a String
|
|
69
|
+
# @return [Boolean] true if a live allowance lets `from_role` act as `to_role`
|
|
70
|
+
# @raise [Runtime::WiringError] unless exactly one chapter provides `"authorization"`
|
|
110
71
|
def authorized_as?(registry, from_role:, to_role:)
|
|
111
72
|
rows = Runtime::Dispatcher.new(registry).query(
|
|
112
73
|
provided_verb(registry, :transitions),
|
|
@@ -116,12 +77,7 @@ module Hecks
|
|
|
116
77
|
rows.any? { |row| row[:ends_at].nil? }
|
|
117
78
|
end
|
|
118
79
|
|
|
119
|
-
#
|
|
120
|
-
# `AssignmentsForActor` query `holds_role?` runs, just returning
|
|
121
|
-
# the live (non-revoked) row's `role_name` instead of comparing it
|
|
122
|
-
# against a caller-supplied guess. `nil` for no live assignment at
|
|
123
|
-
# all — the caller's own fallback (an aggregate's own role field,
|
|
124
|
-
# a default) is domain-specific and does not belong here.
|
|
80
|
+
# Returns the actor's live role name, or nil; any fallback is the caller's.
|
|
125
81
|
#
|
|
126
82
|
# @param registry [Runtime::Registry] the booted registry, to resolve the authorization
|
|
127
83
|
# provider's verb
|
|
@@ -140,11 +96,8 @@ module Hecks
|
|
|
140
96
|
live && live[:role_name][:value]
|
|
141
97
|
end
|
|
142
98
|
|
|
143
|
-
#
|
|
144
|
-
#
|
|
145
|
-
# loaded chapter declares it (Governance's, in every boot today).
|
|
146
|
-
# Exactly one provider, the same "the runtime will not choose for
|
|
147
|
-
# you" rule `Ports::Authorization.adapter` applies to adapters.
|
|
99
|
+
# Reads a verb from the one chapter that `provides "authorization"`; the runtime will
|
|
100
|
+
# not choose between several (as in `Ports::Authorization.adapter`).
|
|
148
101
|
#
|
|
149
102
|
# @param registry [Runtime::Registry] the booted registry to search for the
|
|
150
103
|
# `"authorization"` provider
|
|
@@ -22,35 +22,13 @@ module Hecks
|
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
#
|
|
26
|
-
# automatically after an ordinary save/delete. Heki's journal is
|
|
27
|
-
# not a disposable write-ahead log: it is this adapter's own
|
|
28
|
-
# answer to `entries`, and `entries` is a real port contract
|
|
29
|
-
# (`Ports::Persistence::AppendOnly`'s own required-methods list)
|
|
30
|
-
# read in full, forever, by `Ports::Projection::Worker#catch_up!`
|
|
31
|
-
# and `Registry#projection_current?` to catch a projection up to
|
|
32
|
-
# its authoritative source, and by `bin/history` to show "every
|
|
33
|
-
# journal entry a domain's append-only adapters hold" — the same
|
|
34
|
-
# contract Postgres/Sqlite/D1 uphold by way of a journal table
|
|
35
|
-
# that is never pruned. A real example (`examples/banking`,
|
|
36
|
-
# `persisted_by("Heki")` + `projected_by("SqliteProjection")`)
|
|
37
|
-
# depends on this today. Compacting throws that full history away
|
|
38
|
-
# for whatever happened before the call — correct only for an
|
|
39
|
-
# aggregate nothing ever projects from; callers (`bin/
|
|
40
|
-
# heki_compact`) are responsible for confirming that first.
|
|
25
|
+
# Folds the journal into the snapshot and truncates it. Opt-in; never run after a save.
|
|
41
26
|
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
# two leaves old (now fully redundant) journal lines in place;
|
|
48
|
-
# replaying them again over the fresh snapshot on the next boot
|
|
49
|
-
# is idempotent — the same value gets set again, never a wrong
|
|
50
|
-
# one — so nothing is lost, just a little wasted replay work
|
|
51
|
-
# once. A crash before `write` completes leaves the journal
|
|
52
|
-
# fully intact and the prior snapshot untouched, exactly today's
|
|
53
|
-
# existing crash-recovery guarantee.
|
|
27
|
+
# This discards the history `entries` returns, which projections and `bin/history`
|
|
28
|
+
# read in full, so use it only on an aggregate nothing projects from.
|
|
29
|
+
#
|
|
30
|
+
# The snapshot is written durably before the journal is truncated; a crash between
|
|
31
|
+
# the two leaves redundant lines whose replay is idempotent.
|
|
54
32
|
#
|
|
55
33
|
# @return [void]
|
|
56
34
|
# @raise [Malformed] if the snapshot or journal file is corrupt
|
|
@@ -68,15 +46,7 @@ module Hecks
|
|
|
68
46
|
def truncate_journal!
|
|
69
47
|
return unless File.exist?(@journal_path)
|
|
70
48
|
|
|
71
|
-
#
|
|
72
|
-
# descriptor — the file's length changes atomically at the
|
|
73
|
-
# filesystem level, so there is no "half truncated" state to
|
|
74
|
-
# observe even under a crash mid-call. `fsync` below makes
|
|
75
|
-
# that change durable before this method returns; without it
|
|
76
|
-
# a crash could still leave the old (harmless-to-replay)
|
|
77
|
-
# content on disk after a normal return, which is fine per
|
|
78
|
-
# the crash-safety note above, but the durable case is the
|
|
79
|
-
# one actually worth returning success for.
|
|
49
|
+
# `truncate(0)` is atomic; the fsync makes it durable before returning.
|
|
80
50
|
File.open(@journal_path, "r+b") do |file|
|
|
81
51
|
file.truncate(0)
|
|
82
52
|
file.flush
|
|
@@ -106,10 +76,7 @@ module Hecks
|
|
|
106
76
|
encoded = Ports::Persistence::StateCodec.encode(@aggregate, state)
|
|
107
77
|
line = "#{JSON.generate(operation: operation, id: id.to_s, state: encoded, mirrors: @entry_mirrors)}\n"
|
|
108
78
|
|
|
109
|
-
# One write,
|
|
110
|
-
# appends can only interleave *between* writes, never inside
|
|
111
|
-
# one, so this line can't come out split by another process's
|
|
112
|
-
# line landing in the middle of it.
|
|
79
|
+
# One write, so a concurrent append cannot land in the middle of this line.
|
|
113
80
|
File.open(@journal_path, "ab") do |journal|
|
|
114
81
|
journal.write(line)
|
|
115
82
|
journal.flush
|
|
@@ -4,46 +4,11 @@ require_relative "journal"
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Adapters
|
|
6
6
|
class Heki
|
|
7
|
-
#
|
|
8
|
-
#
|
|
7
|
+
# Saga checkpoints in a sibling snapshot+journal file pair, keyed by
|
|
8
|
+
# (domain, process manager, correlation).
|
|
9
9
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# Built the exact same way an aggregate's own persistence already
|
|
13
|
-
# is: `Snapshot`/`Journal` (heki/snapshot.rb, heki/journal.rb)
|
|
14
|
-
# operate generically on `@path`/`@journal_path`/`@entry_mirrors`
|
|
15
|
-
# and never touch `@aggregate`, so this reuses them unchanged
|
|
16
|
-
# rather than re-deriving the same binary framing and
|
|
17
|
-
# crash-recovery replay.
|
|
18
|
-
#
|
|
19
|
-
# ## Where the file lives
|
|
20
|
-
#
|
|
21
|
-
# Reserved file name (`hecks_saga_instances.heki`, matching the
|
|
22
|
-
# `hecks_`-prefix convention every other new saga table in this
|
|
23
|
-
# work uses) avoids colliding with any real aggregate's own
|
|
24
|
-
# `storage_name`. Lives in the same directory an aggregate's own
|
|
25
|
-
# `.heki` file would (`File.dirname(@path)`, `Heki`'s own call
|
|
26
|
-
# below) — which, since Heki's `resolve_path` has no per-domain
|
|
27
|
-
# component at all, is typically shared across every domain
|
|
28
|
-
# booted from the same `root`. `domain` is therefore carried
|
|
29
|
-
# inside each record and filtered on read, the same reason
|
|
30
|
-
# Postgres's own `hecks_saga_instances` keeps an explicit `domain`
|
|
31
|
-
# column under schema isolation (§3).
|
|
32
|
-
#
|
|
33
|
-
# ## Record shape
|
|
34
|
-
#
|
|
35
|
-
# One flat records hash, keyed by a composite string (Heki's own
|
|
36
|
-
# snapshot format is id-keyed, not tuple-keyed) — never exposed
|
|
37
|
-
# outside this class; `each_saga` yields the five real fields a
|
|
38
|
-
# caller actually wants, not the internal key shape.
|
|
39
|
-
#
|
|
40
|
-
# ## Locking
|
|
41
|
-
#
|
|
42
|
-
# Locked the same way an aggregate's own store is: `with_lock`
|
|
43
|
-
# (`Snapshot`, shared) serializes each save/delete's read-modify-
|
|
44
|
-
# write against `@path`'s own lock file — a saga gets exactly the
|
|
45
|
-
# durability and concurrency-safety this adapter already gives its
|
|
46
|
-
# aggregates, no better, no worse.
|
|
10
|
+
# The file is shared by every domain booted from the same directory, so `domain`
|
|
11
|
+
# is stored in each record and filtered on read.
|
|
47
12
|
class SagaStore
|
|
48
13
|
include Snapshot
|
|
49
14
|
include Journal
|
|
@@ -59,16 +24,13 @@ module Hecks
|
|
|
59
24
|
# Upserts one saga instance's checkpoint, keyed by domain, process manager and
|
|
60
25
|
# correlation, under the file lock.
|
|
61
26
|
#
|
|
62
|
-
# @param domain [String] the owning domain, carried in the record and filtered on
|
|
63
|
-
# read
|
|
27
|
+
# @param domain [String] the owning domain, carried in the record and filtered on read
|
|
64
28
|
# @param process_manager [String] the process manager's name
|
|
65
29
|
# @param correlation [String] the instance's correlation value
|
|
66
30
|
# @param state [String] the saga's current state name
|
|
67
31
|
# @param memory [Hash] the saga's working memory to persist
|
|
68
|
-
# @param completed_compensations [Array]
|
|
69
|
-
#
|
|
70
|
-
# @return [Hash{String => Hash}] the store's full internal records Hash after the
|
|
71
|
-
# write; callers ignore it
|
|
32
|
+
# @param completed_compensations [Array] completed compensable legs; `[]` when none
|
|
33
|
+
# @return [Hash{String => Hash}] the full records Hash after the write; callers ignore it
|
|
72
34
|
def save_saga(domain, process_manager, correlation, state, memory, completed_compensations = [])
|
|
73
35
|
key = key_for(domain, process_manager, correlation)
|
|
74
36
|
record = { "domain" => domain, "process_manager" => process_manager,
|
|
@@ -104,8 +66,7 @@ module Hecks
|
|
|
104
66
|
end
|
|
105
67
|
end
|
|
106
68
|
|
|
107
|
-
# Yields every checkpointed saga instance of `domain`, for
|
|
108
|
-
# #rehydrate_sagas!` to restore at boot.
|
|
69
|
+
# Yields every checkpointed saga instance of `domain`, for boot-time rehydration.
|
|
109
70
|
#
|
|
110
71
|
# @param domain [String] the owning domain to filter records to
|
|
111
72
|
# @yieldparam process_manager [String] the process manager's name
|
|
@@ -113,10 +74,8 @@ module Hecks
|
|
|
113
74
|
# @yieldparam state [String] the saga's state name
|
|
114
75
|
# @yieldparam memory [Hash{Symbol => Object}] the saga's memory, Symbol keys at every
|
|
115
76
|
# depth
|
|
116
|
-
# @yieldparam completed_compensations [Array] the completed-compensation ledger
|
|
117
|
-
#
|
|
118
|
-
# @return [Enumerator, Hash{String => Hash}] an enumerator over the same five values
|
|
119
|
-
# when no block is given; otherwise the store's full internal records Hash
|
|
77
|
+
# @yieldparam completed_compensations [Array] the completed-compensation ledger
|
|
78
|
+
# @return [Enumerator, Hash{String => Hash}] an enumerator when no block is given
|
|
120
79
|
def each_saga(domain)
|
|
121
80
|
return enum_for(:each_saga, domain) unless block_given?
|
|
122
81
|
|
|
@@ -135,9 +94,8 @@ module Hecks
|
|
|
135
94
|
@store ||= replay_journal(read_snapshot)
|
|
136
95
|
end
|
|
137
96
|
|
|
138
|
-
#
|
|
139
|
-
#
|
|
140
|
-
# here should assume it never contains a space.
|
|
97
|
+
# Space-joined; the key is opaque and never parsed back, so spaces in a
|
|
98
|
+
# correlation value are harmless.
|
|
141
99
|
def key_for(domain, process_manager, correlation) = [domain, process_manager, correlation].join(" ")
|
|
142
100
|
end
|
|
143
101
|
end
|
|
@@ -23,11 +23,7 @@ module Hecks
|
|
|
23
23
|
raise Malformed, "#{@path}: json error: #{e.message}"
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
# Temp
|
|
27
|
-
# only ever see the last complete snapshot or the one before it,
|
|
28
|
-
# never a truncated or partial one — `File.binwrite`'s old
|
|
29
|
-
# truncate-then-write left a window, proportional to the whole
|
|
30
|
-
# dataset, where the file on disk was neither.
|
|
26
|
+
# Temp file, fsync, then rename, so a reader never sees a partial snapshot.
|
|
31
27
|
def write(records)
|
|
32
28
|
sorted = records.sort_by { |id, _| id }.to_h
|
|
33
29
|
json = JSON.generate(sorted)
|
|
@@ -42,13 +38,9 @@ module Hecks
|
|
|
42
38
|
File.rename(tmp, @path)
|
|
43
39
|
end
|
|
44
40
|
|
|
45
|
-
# Serializes
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
# has to go through this to matter, which `with_lock`'s only two
|
|
49
|
-
# callers (`Heki#save`/`#delete`, `SagaStore#save_saga`/
|
|
50
|
-
# `#delete_saga`) both do. The lock file is separate from
|
|
51
|
-
# `@path` so a held lock never blocks `write`'s own rename.
|
|
41
|
+
# Serializes each save/delete's read-modify-write across processes; `flock` is
|
|
42
|
+
# advisory, so every writer must use it. The lock file is separate from `@path`
|
|
43
|
+
# so a held lock never blocks `write`'s rename.
|
|
52
44
|
def with_lock
|
|
53
45
|
File.open(lock_path, File::CREAT | File::RDWR, 0o644) do |lock|
|
|
54
46
|
lock.flock(File::LOCK_EX)
|