hecks 0.2.0 → 0.3.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 +5 -5
- data/lib/hecks/adapters/driven/claude_code.adapter +3 -0
- data/lib/hecks/adapters/driven/claude_code.rb +127 -0
- data/lib/hecks/adapters/driven/d1.adapter +12 -0
- data/lib/hecks/adapters/driven/d1.rb +403 -0
- data/lib/hecks/adapters/driven/folder.adapter +3 -0
- data/lib/hecks/adapters/driven/folder.rb +199 -0
- data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +100 -0
- data/lib/hecks/adapters/driven/governance_authorization.adapter +3 -0
- data/lib/hecks/adapters/driven/governance_authorization.rb +62 -0
- data/lib/hecks/adapters/driven/heki/journal.rb +51 -0
- data/lib/hecks/adapters/driven/heki/saga_store.rb +0 -0
- data/lib/hecks/adapters/driven/heki/snapshot.rb +38 -0
- data/lib/hecks/adapters/driven/heki.adapter +4 -0
- data/lib/hecks/adapters/driven/heki.rb +148 -0
- data/lib/hecks/adapters/driven/identity_registry.adapter +3 -0
- data/lib/hecks/adapters/driven/identity_registry.rb +26 -0
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +51 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +63 -0
- data/lib/hecks/adapters/driven/lambda.adapter +4 -0
- data/lib/hecks/adapters/driven/lambda.rb +131 -0
- data/lib/hecks/adapters/driven/memory.adapter +3 -0
- data/lib/hecks/adapters/driven/memory.rb +103 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.adapter +3 -0
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +28 -0
- data/lib/hecks/adapters/driven/postgres/codec.rb +80 -0
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +168 -0
- data/lib/hecks/adapters/driven/postgres.adapter +5 -0
- data/lib/hecks/adapters/driven/postgres.rb +391 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/era_store.rb +171 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/field_cache.rb +190 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/head_compiler.rb +429 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/mint_transaction.rb +166 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/provisioning.rb +286 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/resumable_backfill.rb +168 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/tail_merge.rb +163 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage/transform_installer.rb +114 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage.rb +137 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/coverage_check.rb +89 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/era_resolver.rb +82 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/merge_coordinator.rb +43 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager/minter.rb +132 -0
- data/lib/hecks/adapters/driven/postgres_era/lineage_manager.rb +74 -0
- data/lib/hecks/adapters/driven/postgres_era.adapter +6 -0
- data/lib/hecks/adapters/driven/postgres_era.rb +671 -0
- data/lib/hecks/adapters/driven/prism.adapter +3 -0
- data/lib/hecks/adapters/driven/prism.rb +80 -0
- data/lib/hecks/adapters/driven/secure_random_identity.adapter +3 -0
- data/lib/hecks/adapters/driven/secure_random_identity.rb +14 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +221 -0
- data/lib/hecks/adapters/driven/sqlite/codec.rb +63 -0
- data/lib/hecks/adapters/driven/sqlite/projection.rb +105 -0
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +180 -0
- data/lib/hecks/adapters/driven/sqlite.adapter +9 -0
- data/lib/hecks/adapters/driven/sqlite.rb +285 -0
- data/lib/hecks/adapters/driven/system_clock.adapter +3 -0
- data/lib/hecks/adapters/driven/system_clock.rb +14 -0
- data/lib/hecks/adapters/driven.rb +36 -0
- data/lib/hecks/adapters.rb +6 -0
- data/lib/hecks/behaviors/dsl.rb +96 -0
- data/lib/hecks/behaviors/expectations.rb +280 -0
- data/lib/hecks/behaviors/ir.rb +31 -0
- data/lib/hecks/behaviors/rspec.rb +42 -0
- data/lib/hecks/behaviors/runner.rb +96 -0
- data/lib/hecks/behaviors.rb +25 -0
- data/lib/hecks/bluebook/aggregate.rb +108 -0
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +134 -0
- data/lib/hecks/bluebook/assembly/build.rb +48 -0
- data/lib/hecks/bluebook/assembly/contract.rb +112 -0
- data/lib/hecks/bluebook/assembly/contracts.rb +409 -0
- data/lib/hecks/bluebook/assembly/marks.rb +227 -0
- data/lib/hecks/bluebook/assembly/specializer.rb +70 -0
- data/lib/hecks/bluebook/assembly.rb +78 -0
- data/lib/hecks/bluebook/attribute.rb +96 -0
- data/lib/hecks/bluebook/behaviour/aggregate.rb +83 -0
- data/lib/hecks/bluebook/behaviour/attribute.rb +27 -0
- data/lib/hecks/bluebook/behaviour/chapter.rb +72 -0
- data/lib/hecks/bluebook/behaviour/command.rb +116 -0
- data/lib/hecks/bluebook/behaviour/domain_port.rb +25 -0
- data/lib/hecks/bluebook/behaviour/entity.rb +59 -0
- data/lib/hecks/bluebook/behaviour/hexagon.rb +50 -0
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +51 -0
- data/lib/hecks/bluebook/behaviour/policy.rb +52 -0
- data/lib/hecks/bluebook/behaviour/process_manager.rb +38 -0
- data/lib/hecks/bluebook/behaviour/query.rb +10 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -0
- data/lib/hecks/bluebook/behaviour/traits.rb +81 -0
- data/lib/hecks/bluebook/behaviour/value_object.rb +33 -0
- data/lib/hecks/bluebook/chapter.rb +68 -0
- data/lib/hecks/bluebook/command.rb +124 -0
- data/lib/hecks/bluebook/domain_port.rb +102 -0
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +34 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +906 -0
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +327 -0
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +71 -0
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +1018 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +626 -0
- data/lib/hecks/bluebook/dsl/const_shim.rb +81 -0
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +79 -0
- data/lib/hecks/bluebook/dsl/entity_builder.rb +305 -0
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +366 -0
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +163 -0
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +191 -0
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +44 -0
- data/lib/hecks/bluebook/dsl/malformed.rb +7 -0
- data/lib/hecks/bluebook/dsl/policy_builder.rb +120 -0
- data/lib/hecks/bluebook/dsl/port_builder.rb +30 -0
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +142 -0
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +207 -0
- data/lib/hecks/bluebook/dsl/query_builder.rb +113 -0
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +233 -0
- data/lib/hecks/bluebook/dsl/rule_reference.rb +172 -0
- data/lib/hecks/bluebook/dsl/translation_builder.rb +243 -0
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +178 -0
- data/lib/hecks/bluebook/dsl/word_gate.rb +221 -0
- data/lib/hecks/bluebook/dsl/world_builder.rb +77 -0
- data/lib/hecks/bluebook/dsl.rb +45 -0
- data/lib/hecks/bluebook/entity.rb +103 -0
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -0
- data/lib/hecks/bluebook/expression/evaluator.rb +261 -0
- data/lib/hecks/bluebook/expression/projection.json +170 -0
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +233 -0
- data/lib/hecks/bluebook/expression/resolver.rb +630 -0
- data/lib/hecks/bluebook/expression.rb +13 -0
- data/lib/hecks/bluebook/hexagon.rb +60 -0
- data/lib/hecks/bluebook/lifecycle.rb +42 -0
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +54 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +559 -0
- data/lib/hecks/bluebook/meta_validator/plan.rb +332 -0
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +47 -0
- data/lib/hecks/bluebook/meta_validator/readings.rb +350 -0
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +312 -0
- data/lib/hecks/bluebook/meta_validator/shapes.rb +246 -0
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +255 -0
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +138 -0
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +78 -0
- data/lib/hecks/bluebook/meta_validator.rb +452 -0
- data/lib/hecks/bluebook/model_check.rb +318 -0
- data/lib/hecks/bluebook/pattern_subset.rb +120 -0
- data/lib/hecks/bluebook/policy.rb +41 -0
- data/lib/hecks/bluebook/process_manager.rb +86 -0
- data/lib/hecks/bluebook/project_discovery.rb +30 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +82 -0
- data/lib/hecks/bluebook/query.rb +61 -0
- data/lib/hecks/bluebook/read_model.rb +109 -0
- data/lib/hecks/bluebook/reference.rb +74 -0
- data/lib/hecks/bluebook/smoke_test.rb +166 -0
- data/lib/hecks/bluebook/synthesizer.rb +95 -0
- data/lib/hecks/bluebook/translation.rb +92 -0
- data/lib/hecks/bluebook/value_object.rb +58 -0
- data/lib/hecks/bluebook.rb +74 -0
- data/lib/hecks/codemod.rb +342 -0
- data/lib/hecks/construct.rb +71 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +219 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +4 -0
- data/lib/hecks/deploy/oidc.json +18 -0
- data/lib/hecks/doc/reference.rb +389 -0
- data/lib/hecks/embryonaut_bluebook.rb +75 -0
- data/lib/hecks/facade/cli_door.rb +116 -0
- data/lib/hecks/facade/cli_runner.rb +190 -0
- data/lib/hecks/facade/command_request.rb +105 -0
- data/lib/hecks/facade/handle.rb +173 -0
- data/lib/hecks/facade/json_door.rb +154 -0
- data/lib/hecks/facade/surface/aggregate_door.rb +185 -0
- data/lib/hecks/facade/surface/chapter.rb +107 -0
- data/lib/hecks/facade/surface.rb +48 -0
- data/lib/hecks/facade.rb +44 -0
- data/lib/hecks/forms/app.rb +300 -0
- data/lib/hecks/forms/command_form_renderer.rb +113 -0
- data/lib/hecks/forms/examples/banking_console.bluebook +3 -0
- data/lib/hecks/forms/field_renderer.rb +170 -0
- data/lib/hecks/forms/field_shape.rb +232 -0
- data/lib/hecks/forms/html.rb +53 -0
- data/lib/hecks/forms/index_renderer.rb +35 -0
- data/lib/hecks/forms/page.rb +157 -0
- data/lib/hecks/forms/params.rb +132 -0
- data/lib/hecks/forms/query_form_renderer.rb +114 -0
- data/lib/hecks/forms/record_renderer.rb +115 -0
- data/lib/hecks/forms/record_table.rb +63 -0
- data/lib/hecks/forms/reference_options.rb +30 -0
- data/lib/hecks/forms/value_object_shape.rb +46 -0
- data/lib/hecks/forms.rb +54 -0
- data/lib/hecks/fqn.rb +94 -0
- data/lib/hecks/framework/bluebook/compliance.bluebook +1 -0
- data/lib/hecks/framework/bluebook/console_settings.bluebook +489 -0
- data/lib/hecks/framework/bluebook/framework.hecksagon +32 -0
- data/lib/hecks/framework/bluebook/governance.bluebook +130 -0
- data/lib/hecks/framework/bluebook/identity.bluebook +90 -0
- data/lib/hecks/framework/oidc.json +39 -0
- data/lib/hecks/framework.rb +90 -0
- data/lib/hecks/freezer.rb +67 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +86 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +92 -0
- data/lib/hecks/fuzzing/properties.rb +1146 -0
- data/lib/hecks/fuzzing/replay.rb +635 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +93 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +54 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +96 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +185 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +130 -0
- data/lib/hecks/fuzzing/value_generator.rb +177 -0
- data/lib/hecks/fuzzing.rb +12 -0
- data/lib/hecks/grammar/evolve.rb +291 -0
- data/lib/hecks/grammar/expression.bluebook +417 -0
- data/lib/hecks/grammar/expression_operators.json +1152 -0
- data/lib/hecks/grammar/grammar.hecksagon +20 -0
- data/lib/hecks/grammar/oidc.json +69 -0
- data/lib/hecks/grammar/translation.bluebook +203 -0
- data/lib/hecks/grammar.rb +178 -0
- data/lib/hecks/ir.rb +126 -0
- data/lib/hecks/language/adapter.bluebook +116 -0
- data/lib/hecks/language/bluebook/aggregate.bluebook +581 -0
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +76 -0
- data/lib/hecks/language/bluebook/bluebook.bluebook +256 -0
- data/lib/hecks/language/bluebook/bluebook.hecksagon +24 -0
- data/lib/hecks/language/bluebook/command.bluebook +437 -0
- data/lib/hecks/language/bluebook/entity.bluebook +316 -0
- data/lib/hecks/language/bluebook/policy.bluebook +172 -0
- data/lib/hecks/language/bluebook/process_manager.bluebook +264 -0
- data/lib/hecks/language/bluebook/projection.bluebook +267 -0
- data/lib/hecks/language/bluebook/query.bluebook +245 -0
- data/lib/hecks/language/bluebook/shape.bluebook +292 -0
- data/lib/hecks/language/bluebook/syntax.bluebook +447 -0
- data/lib/hecks/language/bluebook/vocabulary.bluebook +364 -0
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +51 -0
- data/lib/hecks/language/hecksagon/domain_port.bluebook +76 -0
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +131 -0
- data/lib/hecks/language/hecksagon/port_operation.bluebook +102 -0
- data/lib/hecks/language/oidc.json +313 -0
- data/lib/hecks/language/port.bluebook +92 -0
- data/lib/hecks/language/translation/translation.bluebook +110 -0
- data/lib/hecks/language/translation/translation_aggregate.bluebook +267 -0
- data/lib/hecks/language/world/wiring.bluebook +62 -0
- data/lib/hecks/language/world/world.bluebook +84 -0
- data/lib/hecks/literal.rb +125 -0
- data/lib/hecks/naming.rb +121 -0
- data/lib/hecks/ports/access_control.port +4 -0
- data/lib/hecks/ports/access_control.rb +62 -0
- data/lib/hecks/ports/agent/answers.rb +104 -0
- data/lib/hecks/ports/agent.port +4 -0
- data/lib/hecks/ports/agent.rb +167 -0
- data/lib/hecks/ports/authentication.port +4 -0
- data/lib/hecks/ports/authentication.rb +50 -0
- data/lib/hecks/ports/authorization.port +4 -0
- data/lib/hecks/ports/authorization.rb +59 -0
- data/lib/hecks/ports/clock.port +4 -0
- data/lib/hecks/ports/clock.rb +62 -0
- data/lib/hecks/ports/extraction.port +4 -0
- data/lib/hecks/ports/extraction.rb +37 -0
- data/lib/hecks/ports/identity_assignment.port +4 -0
- data/lib/hecks/ports/identity_assignment.rb +45 -0
- data/lib/hecks/ports/identity_generation.port +4 -0
- data/lib/hecks/ports/identity_generation.rb +49 -0
- data/lib/hecks/ports/identity_resolution.port +4 -0
- data/lib/hecks/ports/identity_resolution.rb +40 -0
- data/lib/hecks/ports/loading.port +4 -0
- data/lib/hecks/ports/loading.rb +13 -0
- data/lib/hecks/ports/persistence/append_only.rb +102 -0
- data/lib/hecks/ports/persistence/binding_policy.rb +56 -0
- data/lib/hecks/ports/persistence/execution.rb +18 -0
- data/lib/hecks/ports/persistence/lineage.rb +277 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +25 -0
- data/lib/hecks/ports/persistence/remote_runtime.rb +42 -0
- data/lib/hecks/ports/persistence/repository_factory.rb +30 -0
- data/lib/hecks/ports/persistence.port +4 -0
- data/lib/hecks/ports/persistence.rb +39 -0
- data/lib/hecks/ports/projection.port +4 -0
- data/lib/hecks/ports/projection.rb +76 -0
- data/lib/hecks/ports/query/in_memory.rb +60 -0
- data/lib/hecks/ports/query/ordering.rb +41 -0
- data/lib/hecks/ports/query.rb +36 -0
- data/lib/hecks/ports.rb +27 -0
- data/lib/hecks/projections/diagrams.rb +413 -0
- data/lib/hecks/projections/ir.rb +18 -0
- data/lib/hecks/projections/model/deviations.rb +98 -0
- data/lib/hecks/projections/model.rb +145 -0
- data/lib/hecks/projections/oidc.rb +110 -0
- data/lib/hecks/projections/parser_table.rb +159 -0
- data/lib/hecks/projections/reference.rb +38 -0
- data/lib/hecks/projections/shape.rb +31 -0
- data/lib/hecks/projections/statements.rb +110 -0
- data/lib/hecks/projections/vocabulary.rb +100 -0
- data/lib/hecks/projections.rb +32 -0
- data/lib/hecks/projector/cli_projector.rb +437 -0
- data/lib/hecks/projector/docs_projector.rb +321 -0
- data/lib/hecks/projector/exporter.rb +141 -0
- data/lib/hecks/projector/ir_projector.rb +18 -0
- data/lib/hecks/projector/narrate_projector.rb +243 -0
- data/lib/hecks/projector/target.rb +97 -0
- data/lib/hecks/projector.rb +186 -0
- data/lib/hecks/query_ir.rb +392 -0
- data/lib/hecks/query_specification/common/authorization_spec.rb +9 -0
- data/lib/hecks/query_specification/common/comparators.rb +25 -0
- data/lib/hecks/query_specification/common/comparison.rb +174 -0
- data/lib/hecks/query_specification/common/cursor_spec.rb +9 -0
- data/lib/hecks/query_specification/common/dsl.rb +58 -0
- data/lib/hecks/query_specification/common/inspection_spec.rb +9 -0
- data/lib/hecks/query_specification/common/limit_spec.rb +9 -0
- data/lib/hecks/query_specification/common/null_policy.rb +80 -0
- data/lib/hecks/query_specification/common/null_semantics.rb +11 -0
- data/lib/hecks/query_specification/common/offset_spec.rb +9 -0
- data/lib/hecks/query_specification/common/options.rb +32 -0
- data/lib/hecks/query_specification/common/order_by.rb +9 -0
- data/lib/hecks/query_specification/common/specification.rb +10 -0
- data/lib/hecks/query_specification/common/where_clause.rb +9 -0
- data/lib/hecks/query_specification/field_path.rb +89 -0
- data/lib/hecks/query_specification/hop_path.rb +130 -0
- data/lib/hecks/query_specification/read_model/specification.rb +18 -0
- data/lib/hecks/query_specification.rb +14 -0
- data/lib/hecks/rendering.rb +48 -0
- data/lib/hecks/router/namespace_installer.rb +157 -0
- data/lib/hecks/router.rb +70 -0
- data/lib/hecks/runtime/caller.rb +50 -0
- data/lib/hecks/runtime/capability_graph.rb +44 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +124 -0
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +248 -0
- data/lib/hecks/runtime/command_interpreter.rb +413 -0
- data/lib/hecks/runtime/command_rules/admissibility.rb +307 -0
- data/lib/hecks/runtime/command_rules/arithmetic.rb +254 -0
- data/lib/hecks/runtime/command_rules/authorization.rb +64 -0
- data/lib/hecks/runtime/command_rules/emission.rb +34 -0
- data/lib/hecks/runtime/command_rules/references.rb +181 -0
- data/lib/hecks/runtime/command_rules.rb +28 -0
- data/lib/hecks/runtime/dependency_planning.rb +231 -0
- data/lib/hecks/runtime/dispatcher.rb +286 -0
- data/lib/hecks/runtime/entity_element.rb +253 -0
- data/lib/hecks/runtime/entity_interpreter.rb +228 -0
- data/lib/hecks/runtime/era_check.rb +136 -0
- data/lib/hecks/runtime/era_guard/shape_diff.rb +128 -0
- data/lib/hecks/runtime/era_guard.rb +193 -0
- data/lib/hecks/runtime/era_tamper.rb +61 -0
- data/lib/hecks/runtime/errors.rb +100 -0
- data/lib/hecks/runtime/event.rb +51 -0
- data/lib/hecks/runtime/identity.rb +128 -0
- data/lib/hecks/runtime/instance.rb +120 -0
- data/lib/hecks/runtime/interpreting.rb +69 -0
- data/lib/hecks/runtime/loader.rb +143 -0
- data/lib/hecks/runtime/policy_interpreter.rb +366 -0
- data/lib/hecks/runtime/port_operation_interpreter.rb +210 -0
- data/lib/hecks/runtime/query_interpreter.rb +254 -0
- data/lib/hecks/runtime/reaction_invocation.rb +245 -0
- data/lib/hecks/runtime/read_model_interpreter.rb +266 -0
- data/lib/hecks/runtime/rebuild_sweep.rb +74 -0
- data/lib/hecks/runtime/reference_hop.rb +99 -0
- data/lib/hecks/runtime/refusal_wording.rb +124 -0
- data/lib/hecks/runtime/registry/saga_persistence.rb +70 -0
- data/lib/hecks/runtime/registry/verification.rb +158 -0
- data/lib/hecks/runtime/registry.rb +218 -0
- data/lib/hecks/runtime/remote_dispatcher.rb +143 -0
- data/lib/hecks/runtime/routing.rb +96 -0
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +97 -0
- data/lib/hecks/runtime/saga_interpreter.rb +301 -0
- data/lib/hecks/runtime/storage_shape.rb +120 -0
- data/lib/hecks/runtime/tenant_check.rb +84 -0
- data/lib/hecks/runtime/tenant_scope.rb +55 -0
- data/lib/hecks/runtime/value/admission.rb +112 -0
- data/lib/hecks/runtime/value/coercion.rb +500 -0
- data/lib/hecks/runtime/value/invariant_violation.rb +5 -0
- data/lib/hecks/runtime/value.rb +125 -0
- data/lib/hecks/runtime.rb +95 -0
- data/lib/hecks/translation/audit/approval_digest.rb +31 -0
- data/lib/hecks/translation/audit/layer_one.rb +37 -0
- data/lib/hecks/translation/audit/layer_two.rb +77 -0
- data/lib/hecks/translation/audit/unfed_report.rb +42 -0
- data/lib/hecks/translation/audit.rb +70 -0
- data/lib/hecks/translation/reattest.rb +72 -0
- data/lib/hecks/translation/rule_compiler.rb +120 -0
- data/lib/hecks/translation/scaffold/differ.rb +183 -0
- data/lib/hecks/translation/scaffold/renderer.rb +41 -0
- data/lib/hecks/translation/scaffold/writer.rb +28 -0
- data/lib/hecks/translation/scaffold.rb +30 -0
- data/lib/hecks/translation.rb +11 -0
- data/lib/hecks/version.rb +16 -0
- data/lib/hecks/vocabulary.rb +213 -0
- data/lib/hecks.rb +118 -6
- metadata +401 -222
- data/bin/hecks +0 -7
- data/bin/hecks-package +0 -65
- data/bin/hecks_console +0 -12
- data/bin/hecks_serverless +0 -6
- data/lib/cli/build.rb +0 -14
- data/lib/cli/command_runner.rb +0 -28
- data/lib/cli/console.rb +0 -10
- data/lib/cli/generate.rb +0 -37
- data/lib/cli/hecks-cli.rb +0 -27
- data/lib/cli/test.rb +0 -57
- data/lib/console/commands.rb +0 -8
- data/lib/console/hecks-console.rb +0 -1
- data/lib/packager/README.md +0 -0
- data/lib/packager/app_runner.rb +0 -21
- data/lib/packager/args.rb +0 -26
- data/lib/packager/compatibility/fixnum.rb +0 -6
- data/lib/packager/hecks.rb +0 -39
- data/lib/packager/query_runner.rb +0 -21
- data/lib/packager/resources/Dockerfile +0 -11
- data/lib/packager/resources/app_binary +0 -7
- data/lib/packager/resources/bundle_config +0 -3
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-linux-x86_64.tar.gz +0 -0
- data/lib/packager/resources/traveling-ruby-20150715-2.2.2-osx.tar.gz +0 -0
- data/lib/serverless/Domain +0 -32
- data/lib/serverless/cli.rb +0 -75
- data/lib/serverless/resources/command_name.js +0 -5
- data/lib/serverless/resources/environment.js +0 -7
- data/lib/serverless/resources/handler.js.tt +0 -28
- data/lib/serverless/resources/run_binary.js +0 -22
- data/lib/serverless/resources/serverless.yml +0 -20
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require_relative "../runtime/registry"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
# WHO CAN SIGN IN, AND WHAT ROLE DO THEY HOLD — the domain-specific
|
|
6
|
+
# half of sign-in `Ports::Authentication` deliberately stays out of
|
|
7
|
+
# (see that port's own header: it only ever talks to the external
|
|
8
|
+
# provider, never this registry's own data). Resolved the same way
|
|
9
|
+
# every other port here resolves its adapter: one adapter
|
|
10
|
+
# registry-wide answers this, since an app has no reason to want a
|
|
11
|
+
# different "who's allowed in" source per aggregate.
|
|
12
|
+
#
|
|
13
|
+
# Every domain shapes this differently — who its own "a person who
|
|
14
|
+
# can sign in" aggregate is, what admits them, what grants them
|
|
15
|
+
# access — so this port is pure delegation, same as every sibling
|
|
16
|
+
# port. The one piece of this that IS generic (a live Governance
|
|
17
|
+
# grant should win over whatever an aggregate's own role field
|
|
18
|
+
# says) lives on `Ports::Authorization#live_role_for` instead —
|
|
19
|
+
# a domain's adapter here calls that directly, rather than this
|
|
20
|
+
# port re-deriving Governance-query logic another port already owns.
|
|
21
|
+
module AccessControl
|
|
22
|
+
NAME = "access_control"
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
def session_for_identity(registry, identity_id:)
|
|
27
|
+
adapter(registry).session_for_identity(registry, identity_id: identity_id)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def provision(registry, email:, issuer:, subject:)
|
|
31
|
+
adapter(registry).provision(registry, email: email, issuer: issuer, subject: subject)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def available_roles(registry)
|
|
35
|
+
adapter(registry).available_roles(registry)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def grant(registry, email:, role:)
|
|
39
|
+
adapter(registry).grant(registry, email: email, role: role)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def all_people(registry)
|
|
43
|
+
adapter(registry).all_people(registry)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def adapter(registry)
|
|
47
|
+
implementations = registry.adapters.values.select { |a| a.port == NAME }
|
|
48
|
+
|
|
49
|
+
case implementations.size
|
|
50
|
+
when 1 then Adapters.const_get(implementations.first.name)
|
|
51
|
+
when 0
|
|
52
|
+
raise Runtime::WiringError,
|
|
53
|
+
"no adapter implements the #{NAME} port — nothing can answer who's allowed in"
|
|
54
|
+
else
|
|
55
|
+
raise Runtime::WiringError,
|
|
56
|
+
"#{implementations.size} adapters implement the #{NAME} port " \
|
|
57
|
+
"(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
module Hecks
|
|
2
|
+
module Ports
|
|
3
|
+
module Agent
|
|
4
|
+
# WHERE A RAW HASH BECOMES A STRUCT — the one place, so every
|
|
5
|
+
# adapter (the real `claude_code` one, and any scripted double
|
|
6
|
+
# standing in for it in a spec) is held to the identical shape.
|
|
7
|
+
# An adapter's whole job ends at "here is what came back, already
|
|
8
|
+
# JSON"; whether that hash is USABLE is decided once, here, not
|
|
9
|
+
# re-decided per adapter.
|
|
10
|
+
#
|
|
11
|
+
# Every failure raises `ValidationError` — never lets a bare
|
|
12
|
+
# `NoMethodError` on nil, or a `KeyError`, reach a caller looking
|
|
13
|
+
# for an agent-shaped failure. `bin/interview` catches exactly one
|
|
14
|
+
# class for "the model answered badly," not a grab-bag of Ruby's
|
|
15
|
+
# own.
|
|
16
|
+
module Answers
|
|
17
|
+
VERB_PATTERN = /\A[A-Za-z]+::[A-Za-z]+\.[A-Za-z]+\z/
|
|
18
|
+
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
def questions(raw)
|
|
22
|
+
rows(raw, "questions").map { |row| Question.new(text: text!(row, "text"), because: text!(row, "because")) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def proposals(raw)
|
|
26
|
+
rows(raw, "proposals").map do |row|
|
|
27
|
+
Proposal.new(verb: verb!(row), rationale: text!(row, "rationale"), arguments: arguments!(row))
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def findings(raw)
|
|
32
|
+
rows(raw, "findings").map do |row|
|
|
33
|
+
Finding.new(kind: kind!(row), severity: severity!(row), subject: text!(row, "subject"),
|
|
34
|
+
message: text!(row, "message"))
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def suggestions(raw)
|
|
39
|
+
rows(raw, "names").map do |row|
|
|
40
|
+
Suggestion.new(name: text!(row, "name"), because: text!(row, "because"),
|
|
41
|
+
rejected: Array(row["rejected"]).map(&:to_s))
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# ── shared checks, each named for what it refuses ───────────────
|
|
46
|
+
|
|
47
|
+
def rows(raw, key)
|
|
48
|
+
raise ValidationError, "expected a Hash back, got #{raw.class}: #{raw.inspect}" unless raw.is_a?(Hash)
|
|
49
|
+
|
|
50
|
+
value = raw[key]
|
|
51
|
+
return value if value.is_a?(Array)
|
|
52
|
+
|
|
53
|
+
raise ValidationError, "no #{key.inspect} array in the answer: #{raw.inspect}"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def text!(row, key)
|
|
57
|
+
value = row[key]
|
|
58
|
+
return value.to_s if value && !value.to_s.strip.empty?
|
|
59
|
+
|
|
60
|
+
raise ValidationError, "#{key.inspect} missing or blank in #{row.inspect}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# A VERB THE LANGUAGE COULD NOT EVEN PARSE IS AN ADAPTER FAULT,
|
|
64
|
+
# refused right here — the same `not_fully_qualified` shape the
|
|
65
|
+
# language's own grammar already refuses by, reused rather than
|
|
66
|
+
# reinvented. A verb naming a real category that turns out to
|
|
67
|
+
# describe the wrong fact is NOT this port's business: that one
|
|
68
|
+
# dispatches, and `Interview::Session#offer` is what says no.
|
|
69
|
+
def verb!(row)
|
|
70
|
+
verb = row["verb"].to_s
|
|
71
|
+
return verb if VERB_PATTERN.match?(verb)
|
|
72
|
+
|
|
73
|
+
raise ValidationError, "#{verb.inspect} is not a fully-qualified verb (Chapter::Aggregate.Command)"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def kind!(row)
|
|
77
|
+
kind = row["kind"].to_s.to_sym
|
|
78
|
+
return kind if CRITIQUE_KINDS.include?(kind)
|
|
79
|
+
|
|
80
|
+
raise ValidationError, "#{kind.inspect} is not a critique kind this port knows (#{CRITIQUE_KINDS.join(', ')})"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def severity!(row)
|
|
84
|
+
severity = row["severity"].to_s.to_sym
|
|
85
|
+
return severity if SEVERITIES.include?(severity)
|
|
86
|
+
|
|
87
|
+
raise ValidationError, "#{severity.inspect} is not a severity this port knows (#{SEVERITIES.join(', ')})"
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# ROWS SHAPED EXACTLY AS `Interview::Proposal::Argument` — a
|
|
91
|
+
# reference's `field` is legitimately blank (a reference IS an
|
|
92
|
+
# id), so only `name` is required here.
|
|
93
|
+
def arguments!(row)
|
|
94
|
+
Array(row["arguments"]).map do |argument|
|
|
95
|
+
name = argument["name"].to_s
|
|
96
|
+
raise ValidationError, "argument row has no name: #{argument.inspect}" if name.empty?
|
|
97
|
+
|
|
98
|
+
{ name: name, field: argument["field"].to_s, value: argument["value"].to_s }
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
require_relative "../runtime/registry"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
# THE INTERVIEWER — whatever asks the next good question, reads a
|
|
6
|
+
# sentence back into proposed declarations, judges a model on taste
|
|
7
|
+
# rather than structure, or suggests a name. Resolved the singleton
|
|
8
|
+
# way every other port here is (`Ports::IdentityGeneration`'s own
|
|
9
|
+
# zero/one/many — one interviewer per process, never per-aggregate),
|
|
10
|
+
# because a second adapter answering this port is exactly as
|
|
11
|
+
# unchoosable as a second one minting identities.
|
|
12
|
+
#
|
|
13
|
+
# BUILT LAST, ON PURPOSE, AND STILL OPTIONAL. `.claude/skills/
|
|
14
|
+
# interview/SKILL.md` conducts a whole session with NONE of this —
|
|
15
|
+
# Claude Code reads `bin/interview state` itself, asks the human
|
|
16
|
+
# directly with real conversation context, and calls `bin/interview
|
|
17
|
+
# ask`/`propose`/`accept` itself. This port exists for what that
|
|
18
|
+
# cannot reach: a HEADLESS run (`bin/interview suggest`, no model in
|
|
19
|
+
# the room otherwise), a batch `critique` over an already-written
|
|
20
|
+
# chapter, and — the actual point of building it at all — a
|
|
21
|
+
# SCRIPTED double (`spec/fixtures/scripted_agent.rb`) that makes the
|
|
22
|
+
# whole loop testable with no real model involved.
|
|
23
|
+
#
|
|
24
|
+
# WHAT THIS PORT IS NOT. It does not judge whether a declaration is
|
|
25
|
+
# WELL-FORMED — that is the meta-domain's own job, answered by a real
|
|
26
|
+
# dispatch refusing or not (`Interview::Session#offer`). This port
|
|
27
|
+
# only ever produces SHAPE: a question, a proposed declaration, a
|
|
28
|
+
# judgement about taste, a name. A proposal naming a category the
|
|
29
|
+
# language does not declare is an ADAPTER fault, refused HERE, in
|
|
30
|
+
# `Answers`; a proposal naming a real category but describing the
|
|
31
|
+
# wrong domain fact is dispatched anyway, so the LANGUAGE gets to say
|
|
32
|
+
# why. That line is the whole design.
|
|
33
|
+
#
|
|
34
|
+
# PARSING BELONGS TO THIS FILE, NOT THE ADAPTER. An adapter answers
|
|
35
|
+
# with a plain, already-JSON-shaped Hash (a real model's parsed
|
|
36
|
+
# reply, or a spec double's own hand-built one) — never a Struct —
|
|
37
|
+
# so every adapter is validated identically here rather than trusting
|
|
38
|
+
# each one to refuse the same way. Two adapters that parsed
|
|
39
|
+
# differently would mean the SAME malformed answer passing through
|
|
40
|
+
# one and refusing through the other.
|
|
41
|
+
module Agent
|
|
42
|
+
NAME = "agent"
|
|
43
|
+
|
|
44
|
+
# The answer came back and could not be used — no JSON in it, a
|
|
45
|
+
# category the language does not declare, a severity outside the
|
|
46
|
+
# two that exist. A foreign failure (JSON::ParserError, a missing
|
|
47
|
+
# key) wrapped into something this port owns, the same shape
|
|
48
|
+
# `Ports::Authentication::ValidationError` already is, so no
|
|
49
|
+
# caller ever rescues a raw parser error leaking out of an
|
|
50
|
+
# adapter.
|
|
51
|
+
ValidationError = Class.new(StandardError)
|
|
52
|
+
|
|
53
|
+
# Nothing answered at all — the binary is missing, the subprocess
|
|
54
|
+
# died, the call timed out. Separate from ValidationError on
|
|
55
|
+
# purpose: unavailable means retry or fall back to asking the
|
|
56
|
+
# human directly; malformed means the PROMPT is wrong, not the
|
|
57
|
+
# transport, and retrying identically will not fix it.
|
|
58
|
+
Unavailable = Class.new(StandardError)
|
|
59
|
+
|
|
60
|
+
# ── what the four operations hand back ──────────────────────────
|
|
61
|
+
#
|
|
62
|
+
# Structs, not hashes, for the reason `Bluebook::ModelCheck::Finding`
|
|
63
|
+
# already is one: a `keyword_init` Struct raises on a key nobody
|
|
64
|
+
# declared, which turns "the model invented a field" into a
|
|
65
|
+
# failure at the boundary rather than a nil three calls in.
|
|
66
|
+
|
|
67
|
+
# ONE QUESTION. `because` is the reasoning quoted back so a human
|
|
68
|
+
# can see it, not decoration — the same "show your work" the
|
|
69
|
+
# language's own refusal messages already practice.
|
|
70
|
+
Question = Struct.new(:text, :because, keyword_init: true)
|
|
71
|
+
|
|
72
|
+
# ONE PROPOSED DECLARATION, already shaped as
|
|
73
|
+
# `Interview::Proposal`'s own `Argument` rows — `{name:, field:,
|
|
74
|
+
# value:}`, the exact triple `bin/interview propose --arg
|
|
75
|
+
# name:field:value` already takes and `Interview::Lowering`
|
|
76
|
+
# already knows how to lower. Nothing here decides HOW to address
|
|
77
|
+
# a record or whether a value is bare or wrapped — that was
|
|
78
|
+
# already the proposal-writer's job in `interview.bluebook`'s own
|
|
79
|
+
# design (see `Argument`'s own comment there); this only adds the
|
|
80
|
+
# PROSE reasoning a human typed does not carry on its own.
|
|
81
|
+
Proposal = Struct.new(:verb, :arguments, :rationale, keyword_init: true)
|
|
82
|
+
|
|
83
|
+
# CRITIQUE REUSES `Bluebook::ModelCheck::Finding`'S OWN SHAPE —
|
|
84
|
+
# same fields, same severities, so a mechanical finding
|
|
85
|
+
# (`Session#gaps`) and a judgement (this) print in one list and
|
|
86
|
+
# sort together. The KIND vocabulary is NOT shared — ModelCheck's
|
|
87
|
+
# eleven kinds are structural facts about a graph; these are
|
|
88
|
+
# opinions about a model, closed here on purpose (an open kind
|
|
89
|
+
# field is an open prompt, and an open prompt drifts).
|
|
90
|
+
CRITIQUE_KINDS = %i[
|
|
91
|
+
anemic_aggregate wrong_boundary crud_verb leaky_value_object
|
|
92
|
+
missing_lifecycle missing_invariant ubiquitous_language
|
|
93
|
+
overreaching_identity untold_rule
|
|
94
|
+
].freeze
|
|
95
|
+
SEVERITIES = %i[error warning].freeze
|
|
96
|
+
|
|
97
|
+
Finding = Struct.new(:kind, :severity, :subject, :message, keyword_init: true) do
|
|
98
|
+
def to_s = "#{severity.to_s.upcase.ljust(7)} #{kind.to_s.ljust(20)} #{subject} — #{message}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# ONE SUGGESTED NAME. `rejected` carries the near-misses and why
|
|
102
|
+
# they were passed over — free at the point the model is choosing
|
|
103
|
+
# anyway, and the part a human actually learns from.
|
|
104
|
+
Suggestion = Struct.new(:name, :because, :rejected, keyword_init: true)
|
|
105
|
+
|
|
106
|
+
module_function
|
|
107
|
+
|
|
108
|
+
# THE NEXT BEST QUESTION. `state` is the whole picture — normally
|
|
109
|
+
# `Interview::Session#declaration` plus `#gaps`, so an adapter
|
|
110
|
+
# needs no memory of its own between calls; that is what lets a
|
|
111
|
+
# headless run be a series of one-shot processes, same as
|
|
112
|
+
# `bin/interview` itself already is.
|
|
113
|
+
def ask(registry, state:, asked: [])
|
|
114
|
+
Answers.questions(adapter(registry).ask(state: state, asked: asked))
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# PROSE -> PROPOSED DECLARATIONS. Returns `[]` when the sentence
|
|
118
|
+
# carried no declaration at all (a clarifying question back from
|
|
119
|
+
# the human, say) — a legitimate answer, not a failure.
|
|
120
|
+
def interpret(registry, prose:, state:)
|
|
121
|
+
Answers.proposals(adapter(registry).interpret(prose: prose, state: state))
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# WHAT IS WRONG WITH THIS AS A MODEL — handed everything already
|
|
125
|
+
# known (the language's own refusals, `Session#gaps`'s mechanical
|
|
126
|
+
# findings) so it spends its judgement on what neither of those
|
|
127
|
+
# can see, rather than restating them.
|
|
128
|
+
def critique(registry, declared:, refusals: [], findings: [])
|
|
129
|
+
Answers.findings(adapter(registry).critique(declared: declared, refusals: refusals, findings: findings))
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# VOCABULARY HELP. `near` is what the chapter already calls
|
|
133
|
+
# things, so a suggestion cannot collide with a name in use.
|
|
134
|
+
#
|
|
135
|
+
# NAMED `suggest_name`, NOT `name` — the plan's own word for this
|
|
136
|
+
# operation, but `Module#name` already exists and is load-bearing
|
|
137
|
+
# everywhere (backtraces, RSpec's own description building,
|
|
138
|
+
# `inspect`): a module-function called `name` SHADOWS it the
|
|
139
|
+
# instant it's defined, and `SomeModule.name` (no args) then
|
|
140
|
+
# raises `ArgumentError: missing keywords` the next time anything
|
|
141
|
+
# — not this file, something else entirely — asks the module its
|
|
142
|
+
# own name. Measured, not theoretical: this exact collision broke
|
|
143
|
+
# RSpec's own failure-message formatting the first time it was
|
|
144
|
+
# named `name` here.
|
|
145
|
+
def suggest_name(registry, meaning:, kind:, near: [])
|
|
146
|
+
Answers.suggestions(adapter(registry).suggest_name(meaning: meaning, kind: kind, near: near))
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def adapter(registry)
|
|
150
|
+
implementations = registry.adapters.values.select { |a| a.port == NAME }
|
|
151
|
+
|
|
152
|
+
case implementations.size
|
|
153
|
+
when 1 then Adapters.const_get(implementations.first.name)
|
|
154
|
+
when 0
|
|
155
|
+
raise Runtime::WiringError,
|
|
156
|
+
"no adapter implements the #{NAME} port — nothing can conduct an interview"
|
|
157
|
+
else
|
|
158
|
+
raise Runtime::WiringError,
|
|
159
|
+
"#{implementations.size} adapters implement the #{NAME} port " \
|
|
160
|
+
"(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
require_relative "agent/answers"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require_relative "../runtime/registry"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
# THE OIDC HANDSHAKE ITSELF — building the URL a browser goes to,
|
|
6
|
+
# and turning the code a provider sends back into a verified
|
|
7
|
+
# (issuer, subject, email) triple. Resolved the same way every
|
|
8
|
+
# other port here resolves its adapter: one adapter registry-wide
|
|
9
|
+
# answers this, not a per-aggregate binding, since a domain has no
|
|
10
|
+
# reason to want a different sign-in provider per aggregate.
|
|
11
|
+
#
|
|
12
|
+
# THE OTHER HALF of "who is this" — what a verified pair MEANS to
|
|
13
|
+
# this app, whether it resolves to an existing Identity — is
|
|
14
|
+
# `Ports::IdentityResolution`'s job, not this one's. This port only
|
|
15
|
+
# ever talks to the external provider; it never touches the
|
|
16
|
+
# registry's own Identity/Governance data (it takes `registry`
|
|
17
|
+
# purely to resolve which adapter answers it, same as every port
|
|
18
|
+
# here already does).
|
|
19
|
+
module Authentication
|
|
20
|
+
NAME = "authentication"
|
|
21
|
+
|
|
22
|
+
ValidationError = Class.new(StandardError)
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
def authorization_url(registry)
|
|
27
|
+
adapter(registry).authorization_url
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def verify(registry, code:, state:, expected_state:)
|
|
31
|
+
adapter(registry).verify(code: code, state: state, expected_state: expected_state)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def adapter(registry)
|
|
35
|
+
implementations = registry.adapters.values.select { |a| a.port == NAME }
|
|
36
|
+
|
|
37
|
+
case implementations.size
|
|
38
|
+
when 1 then Adapters.const_get(implementations.first.name)
|
|
39
|
+
when 0
|
|
40
|
+
raise Runtime::WiringError,
|
|
41
|
+
"no adapter implements the #{NAME} port — nothing can authenticate a sign-in"
|
|
42
|
+
else
|
|
43
|
+
raise Runtime::WiringError,
|
|
44
|
+
"#{implementations.size} adapters implement the #{NAME} port " \
|
|
45
|
+
"(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
require_relative "../runtime/registry"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
# TWO YES/NO QUESTIONS AND ONE VALUE an application asks BEFORE
|
|
6
|
+
# binding a caller — resolved the same way `Ports::IdentityGeneration`
|
|
7
|
+
# resolves its own adapter: one adapter registry-wide answers this
|
|
8
|
+
# port, not a per-aggregate binding, since a domain has no reason to
|
|
9
|
+
# want a different authorization source per aggregate. What answers
|
|
10
|
+
# it is deliberately not named here — the governance-backed adapter
|
|
11
|
+
# is one implementation, not the only possible one, the same way
|
|
12
|
+
# `SequentialIdentity` and `SecureRandomIdentity` are two
|
|
13
|
+
# implementations of `identity_generation`.
|
|
14
|
+
#
|
|
15
|
+
# A caller decides what to DO with the answer — dispatch under that
|
|
16
|
+
# role, refuse, log, prefer it over some other fallback value — this
|
|
17
|
+
# only answers the question asked. Nothing here binds a
|
|
18
|
+
# `Runtime::Caller`, and nothing here is consulted by
|
|
19
|
+
# `CommandRules::Authorization`: that rule still only compares an
|
|
20
|
+
# ALREADY-BOUND caller's role against a command's, the same as it
|
|
21
|
+
# always has. `spec/act_as_spec.rb` remains the precedent for the
|
|
22
|
+
# OTHER shape — two separate registries, queried directly by name —
|
|
23
|
+
# for whenever Governance is not in the same boot as the caller;
|
|
24
|
+
# this port is the same two questions asked through one adapter
|
|
25
|
+
# when it is.
|
|
26
|
+
module Authorization
|
|
27
|
+
NAME = "authorization"
|
|
28
|
+
|
|
29
|
+
module_function
|
|
30
|
+
|
|
31
|
+
def holds_role?(registry, actor_id:, role:)
|
|
32
|
+
adapter(registry).holds_role?(registry, actor_id: actor_id, role: role)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def authorized_as?(registry, from_role:, to_role:)
|
|
36
|
+
adapter(registry).authorized_as?(registry, from_role: from_role, to_role: to_role)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def live_role_for(registry, actor_id:)
|
|
40
|
+
adapter(registry).live_role_for(registry, actor_id: actor_id)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def adapter(registry)
|
|
44
|
+
implementations = registry.adapters.values.select { |a| a.port == NAME }
|
|
45
|
+
|
|
46
|
+
case implementations.size
|
|
47
|
+
when 1 then Adapters.const_get(implementations.first.name)
|
|
48
|
+
when 0
|
|
49
|
+
raise Runtime::WiringError,
|
|
50
|
+
"no adapter implements the #{NAME} port — nothing can answer a role check"
|
|
51
|
+
else
|
|
52
|
+
raise Runtime::WiringError,
|
|
53
|
+
"#{implementations.size} adapters implement the #{NAME} port " \
|
|
54
|
+
"(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
require_relative "../runtime/registry"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
# WHAT TIME IT IS — the one fact a domain cannot derive and must not invent.
|
|
6
|
+
#
|
|
7
|
+
# Resolved exactly the way `Ports::IdentityGeneration` resolves its own
|
|
8
|
+
# adapter: one adapter registry-wide implements this, not a per-aggregate
|
|
9
|
+
# binding, because two aggregates in one running app have no real reason to
|
|
10
|
+
# disagree about the time.
|
|
11
|
+
#
|
|
12
|
+
# WHY A PORT AND NOT `Time.now`. A staleness rule — "another agent may take
|
|
13
|
+
# this claim after fifteen minutes" — is untestable against the real clock:
|
|
14
|
+
# a spec for it would either sleep for fifteen minutes or never run at all.
|
|
15
|
+
# Bound to a fixed adapter, the same rule is three lines. That is the whole
|
|
16
|
+
# argument, and it is the same one `SequentialIdentity` makes next door.
|
|
17
|
+
#
|
|
18
|
+
# A PREDICATE STILL CANNOT ASK THE TIME, and this does not change that. The
|
|
19
|
+
# expression sublanguage has no clock and should not: a `given` that read
|
|
20
|
+
# the time would evaluate differently on two runs over the same record, and
|
|
21
|
+
# every replay, audit and fuzz oracle in this repository assumes it does
|
|
22
|
+
# not. So `now` remains an ARGUMENT the predicate merely reads, and this
|
|
23
|
+
# port is what lets a caller stop typing it — the door fills it in, the
|
|
24
|
+
# value is baked into the dispatch, and the recorded step carries the
|
|
25
|
+
# concrete number forever after.
|
|
26
|
+
#
|
|
27
|
+
# WHICH IS WHY THE RUNTIME DOES NOT CALL THIS. `IdentityGeneration`'s own
|
|
28
|
+
# note works through the replay question for a minted uuid and lands on
|
|
29
|
+
# "the value gets baked into the caller's args at the first live dispatch".
|
|
30
|
+
# A clock consulted INSIDE the interpreter would not have that property: a
|
|
31
|
+
# recorded corpus step replayed tomorrow would silently get tomorrow's
|
|
32
|
+
# time, and the fuzzer's oracle and the adapter-agreement gate both compare
|
|
33
|
+
# runs of exactly that shape. So the filling happens at the door, where a
|
|
34
|
+
# human or an agent is typing, and never on the dispatch path a replay uses.
|
|
35
|
+
module Clock
|
|
36
|
+
NAME = "clock"
|
|
37
|
+
|
|
38
|
+
module_function
|
|
39
|
+
|
|
40
|
+
# SECONDS, AS AN INTEGER, because that is what the sublanguage can do
|
|
41
|
+
# arithmetic on. `claimed_at.value + window.value <= now.value` is
|
|
42
|
+
# addition and comparison — the only two things it has — and a Time
|
|
43
|
+
# object supports neither.
|
|
44
|
+
def now(registry) = adapter(registry).now
|
|
45
|
+
|
|
46
|
+
def adapter(registry)
|
|
47
|
+
implementations = registry.adapters.values.select { |a| a.port == NAME }
|
|
48
|
+
|
|
49
|
+
case implementations.size
|
|
50
|
+
when 1 then Adapters.const_get(implementations.first.name)
|
|
51
|
+
when 0
|
|
52
|
+
raise Runtime::WiringError,
|
|
53
|
+
"no adapter implements the #{NAME} port — nothing can say what time it is"
|
|
54
|
+
else
|
|
55
|
+
raise Runtime::WiringError,
|
|
56
|
+
"#{implementations.size} adapters implement the #{NAME} port " \
|
|
57
|
+
"(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require_relative "../runtime/registry"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
module Extraction
|
|
6
|
+
NAME = "extraction"
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def canonical(block) = adapter.canonical(block)
|
|
11
|
+
|
|
12
|
+
def adapter
|
|
13
|
+
registry = Hecks.current_registry
|
|
14
|
+
unless registry
|
|
15
|
+
raise Runtime::WiringError,
|
|
16
|
+
"extraction resolved outside a boot — a predicate can only be " \
|
|
17
|
+
"read while a bluebook is loading"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
implementations = registry.adapters.values.select { |a| a.port == NAME }
|
|
21
|
+
|
|
22
|
+
case implementations.size
|
|
23
|
+
when 1 then Adapters.const_get(implementations.first.name)
|
|
24
|
+
when 0
|
|
25
|
+
raise Runtime::WiringError,
|
|
26
|
+
"no adapter implements the #{NAME} port — nothing can recover a " \
|
|
27
|
+
"predicate's source"
|
|
28
|
+
else
|
|
29
|
+
raise Runtime::WiringError,
|
|
30
|
+
"#{implementations.size} adapters implement the #{NAME} port " \
|
|
31
|
+
"(#{implementations.map(&:name).sort.join(', ')}) — the runtime " \
|
|
32
|
+
"will not choose for you"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
require_relative "../runtime/registry"
|
|
2
|
+
|
|
3
|
+
module Hecks
|
|
4
|
+
module Ports
|
|
5
|
+
# WHICH VALUE A CREATING COMMAND'S OWN IDENTITY FIELD GETS, when
|
|
6
|
+
# neither a slug-from-another-field nor a sequence-with-prefix
|
|
7
|
+
# mechanically answers it — a driving app's own console, say,
|
|
8
|
+
# falls back to this when a collection's identity strategy names
|
|
9
|
+
# "port" rather than "slug"/"sequence" (both of those stay pure
|
|
10
|
+
# config-driven mechanics, computed by whoever is dispatching,
|
|
11
|
+
# never by this port).
|
|
12
|
+
#
|
|
13
|
+
# Pure delegation, same as every sibling port (AccessControl,
|
|
14
|
+
# IdentityGeneration): an app's own domain declares what "port"
|
|
15
|
+
# actually MEANS for it — mint a real UUID via
|
|
16
|
+
# Ports::IdentityGeneration, derive something else entirely — this
|
|
17
|
+
# port just resolves the one adapter that domain wired and calls
|
|
18
|
+
# it, the same "one adapter registry-wide, refuse if zero or many"
|
|
19
|
+
# resolution every other port here already uses.
|
|
20
|
+
module IdentityAssignment
|
|
21
|
+
NAME = "identity_assignment"
|
|
22
|
+
|
|
23
|
+
module_function
|
|
24
|
+
|
|
25
|
+
def assign(registry, agg_name:, field_name:, args:)
|
|
26
|
+
adapter(registry).assign(registry, agg_name: agg_name, field_name: field_name, args: args)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def adapter(registry)
|
|
30
|
+
implementations = registry.adapters.values.select { |a| a.port == NAME }
|
|
31
|
+
|
|
32
|
+
case implementations.size
|
|
33
|
+
when 1 then Adapters.const_get(implementations.first.name)
|
|
34
|
+
when 0
|
|
35
|
+
raise Runtime::WiringError,
|
|
36
|
+
"no adapter implements the #{NAME} port — nothing can assign an identity"
|
|
37
|
+
else
|
|
38
|
+
raise Runtime::WiringError,
|
|
39
|
+
"#{implementations.size} adapters implement the #{NAME} port " \
|
|
40
|
+
"(#{implementations.map(&:name).sort.join(', ')}) — the runtime will not choose for you"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|