hecks 5.0.0 → 6.0.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/examples/pizzas/bluebook/pizzas.bluebook +1 -0
- data/exe/hecks +14 -2
- data/lib/hecks/adapters/driven/claude_code.rb +1 -1
- data/lib/hecks/adapters/driven/d1.rb +2 -1
- data/lib/hecks/adapters/driven/folder.rb +35 -28
- data/lib/hecks/adapters/driven/folder_overlay.rb +51 -0
- data/lib/hecks/adapters/driven/google_authentication.adapter +3 -0
- data/lib/hecks/adapters/driven/google_authentication.rb +99 -0
- data/lib/hecks/adapters/driven/governance_authorization.rb +32 -36
- data/lib/hecks/adapters/driven/heki/journal.rb +45 -0
- data/lib/hecks/adapters/driven/identity_registry.rb +1 -1
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -2
- data/lib/hecks/adapters/driven/key_keeper.rb +42 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +8 -2
- data/lib/hecks/adapters/driven/lambda/remote_dispatcher.rb +185 -0
- data/lib/hecks/adapters/driven/lambda.rb +14 -2
- data/lib/hecks/adapters/driven/memory/dispatch_claims.rb +25 -0
- data/lib/hecks/adapters/driven/memory.rb +15 -4
- data/lib/hecks/adapters/driven/postgres/codec.rb +1 -1
- data/lib/hecks/adapters/driven/postgres/journal.rb +46 -0
- data/lib/hecks/adapters/driven/postgres/outbox.rb +31 -5
- data/lib/hecks/adapters/driven/postgres/projection.rb +43 -0
- data/lib/hecks/adapters/driven/postgres/repository.rb +1 -1
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +2 -1
- data/lib/hecks/adapters/driven/postgres/set_dialect.rb +44 -0
- data/lib/hecks/adapters/driven/postgres.adapter +8 -0
- data/lib/hecks/adapters/driven/postgres.rb +8 -3
- data/lib/hecks/adapters/driven/postgres_dump/connection.rb +57 -0
- data/lib/hecks/adapters/driven/postgres_dump.rb +117 -0
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/boot.rb +6 -3
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/class_methods.rb +1 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/dialect.rb +1 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/chain_sql.rb +8 -4
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/domain_rename.rb +2 -2
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/era_integrity.rb +2 -2
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/era_store.rb +3 -3
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/mint_transaction.rb +2 -2
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/sql_templates.rb +6 -2
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/tail_merge.rb +1 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage.rb +1 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/context.rb +15 -5
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/coverage_check.rb +13 -11
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/era_resolver.rb +23 -6
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/merge_coordinator.rb +2 -2
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/minter.rb +12 -9
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager/refusals.rb +13 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage_manager.rb +6 -6
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/query_phases.rb +8 -2
- data/lib/hecks/adapters/driven/postgres_era.adapter +7 -1
- data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era.rb +14 -13
- data/lib/hecks/adapters/driven/prism.rb +4 -3
- data/lib/hecks/adapters/driven/projection/read_model_walk.rb +126 -0
- data/lib/hecks/adapters/driven/projection/row_decoding.rb +57 -0
- data/lib/hecks/adapters/driven/set_transform/assignments.rb +101 -0
- data/lib/hecks/adapters/driven/set_transform/expressions.rb +138 -0
- data/lib/hecks/adapters/driven/set_transform.rb +81 -0
- data/lib/hecks/adapters/driven/sql_keyset.rb +61 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +28 -9
- data/lib/hecks/adapters/driven/sqlite/codec.rb +1 -1
- data/lib/hecks/adapters/driven/sqlite/ddl.rb +7 -0
- data/lib/hecks/adapters/driven/sqlite/dialect.rb +2 -1
- data/lib/hecks/adapters/driven/sqlite/dispatch_claims.rb +28 -0
- data/lib/hecks/adapters/driven/sqlite/journal.rb +46 -0
- data/lib/hecks/adapters/driven/sqlite/outbox.rb +3 -2
- data/lib/hecks/adapters/driven/sqlite/projection.rb +9 -135
- data/lib/hecks/adapters/driven/sqlite/repository.rb +1 -1
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +16 -4
- data/lib/hecks/adapters/driven/sqlite/set_dialect.rb +39 -0
- data/lib/hecks/adapters/driven/sqlite.adapter +2 -0
- data/lib/hecks/adapters/driven/sqlite.rb +11 -2
- data/lib/hecks/adapters/driven.rb +8 -3
- data/lib/hecks/adapters/driving/cli.rb +1 -1
- data/lib/hecks/adapters/driving/cli_runner/answers.rb +23 -16
- data/lib/hecks/adapters/driving/cli_runner/resolution.rb +25 -24
- data/lib/hecks/adapters/driving/cli_runner/settling.rb +9 -5
- data/lib/hecks/adapters/driving/cli_runner/streaming.rb +1 -1
- data/lib/hecks/adapters/driving/cli_runner/suggestions.rb +15 -7
- data/lib/hecks/adapters/driving/cli_runner/verb_names.rb +49 -0
- data/lib/hecks/adapters/driving/cli_runner.rb +4 -2
- data/lib/hecks/adapters/driving/github_webhook.rb +2 -1
- data/lib/hecks/adapters/driving/handle/verb_methods.rb +46 -0
- data/lib/hecks/adapters/driving/handle.rb +9 -17
- data/lib/hecks/adapters/driving/json.rb +11 -8
- data/lib/hecks/adapters/driving/launcher_options.rb +37 -32
- data/lib/hecks/adapters/driving/mcp_scope/env_problems.rb +3 -1
- data/lib/hecks/adapters/driving/mcp_scope.rb +8 -7
- data/lib/hecks/adapters/driving/newcomer.rb +6 -5
- data/lib/hecks/adapters/driving/ruby/aggregate_module.rb +13 -12
- data/lib/hecks/adapters/driving/ruby/bulk_verbs.rb +127 -0
- data/lib/hecks/adapters/driving/ruby/{chapter.rb → declaration.rb} +27 -27
- data/lib/hecks/adapters/driving/ruby/query_rows.rb +27 -0
- data/lib/hecks/adapters/driving/ruby.rb +25 -24
- data/lib/hecks/adapters/driving/usage_cache.rb +14 -3
- data/lib/hecks/assembly.rb +101 -0
- data/lib/hecks/behaviors/dsl.rb +13 -3
- data/lib/hecks/behaviors/expectations/boot.rb +7 -6
- data/lib/hecks/behaviors/expectations/checks.rb +1 -1
- data/lib/hecks/behaviors/expectations/dispatching.rb +10 -9
- data/lib/hecks/behaviors/expectations/qualify.rb +9 -9
- data/lib/hecks/behaviors/expectations.rb +24 -10
- data/lib/hecks/behaviors/ir.rb +4 -2
- data/lib/hecks/behaviors/rspec.rb +1 -1
- data/lib/hecks/behaviors/runner.rb +31 -4
- data/lib/hecks/bench/environment.rb +6 -2
- data/lib/hecks/bench/postgres_probe.rb +3 -1
- data/lib/hecks/bench/report.rb +4 -1
- data/lib/hecks/bench/suite.rb +9 -3
- data/lib/hecks/bluebook/acknowledgements.rb +81 -0
- data/lib/hecks/bluebook/aggregate.rb +13 -4
- data/lib/hecks/bluebook/ask_resolution.rb +17 -17
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +8 -5
- data/lib/hecks/bluebook/assembly/contracts/structure.rb +11 -7
- data/lib/hecks/bluebook/assembly/marks.rb +10 -1
- data/lib/hecks/bluebook/assembly/scalars.rb +8 -2
- data/lib/hecks/bluebook/assembly/specializer.rb +2 -2
- data/lib/hecks/bluebook/assembly.rb +10 -10
- data/lib/hecks/bluebook/behaviour/aggregate.rb +2 -1
- data/lib/hecks/bluebook/behaviour/attribute.rb +12 -0
- data/lib/hecks/bluebook/behaviour/command.rb +1 -1
- data/lib/hecks/bluebook/behaviour/{chapter.rb → declaration.rb} +17 -17
- data/lib/hecks/bluebook/behaviour/domain_port.rb +1 -1
- data/lib/hecks/bluebook/behaviour/entity.rb +1 -0
- data/lib/hecks/bluebook/behaviour/hecksagon.rb +8 -1
- data/lib/hecks/bluebook/behaviour/process_manager.rb +4 -1
- data/lib/hecks/bluebook/behaviour/query.rb +5 -0
- data/lib/hecks/bluebook/behaviour/read_model.rb +4 -1
- data/lib/hecks/bluebook/behaviour/traits.rb +13 -0
- data/lib/hecks/bluebook/capabilities/attribute_values.rb +17 -16
- data/lib/hecks/bluebook/capabilities/package_declared.rb +65 -0
- data/lib/hecks/bluebook/capabilities.rb +32 -132
- data/lib/hecks/bluebook/command.rb +22 -7
- data/lib/hecks/bluebook/context_map.rb +104 -0
- data/lib/hecks/bluebook/{chapter.rb → declaration.rb} +13 -13
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +28 -4
- data/lib/hecks/bluebook/dsl/aggregate_builder/draining.rb +2 -2
- data/lib/hecks/bluebook/dsl/aggregate_builder/one_of_reference.rb +54 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/query_field_sealing.rb +11 -2
- data/lib/hecks/bluebook/dsl/aggregate_builder/query_sealing.rb +15 -5
- data/lib/hecks/bluebook/dsl/aggregate_builder/relationships.rb +21 -3
- data/lib/hecks/bluebook/dsl/aggregate_builder/rules.rb +22 -22
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +2 -2
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +27 -22
- data/lib/hecks/bluebook/dsl/bluebook_builder/pending_givens.rb +22 -22
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/computed_sources.rb +27 -21
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/correlation_keys.rb +3 -5
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/event_shapes.rb +5 -4
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/fan_out_rows.rb +3 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/hop_tails.rb +13 -5
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/projected_fields.rb +4 -4
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/provisions.rb +38 -84
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/query_hops.rb +5 -5
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/references.rb +6 -6
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation/with_projections.rb +6 -6
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +3 -3
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +67 -67
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +2 -0
- data/lib/hecks/bluebook/dsl/command_builder/folded_sets.rb +48 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +28 -15
- data/lib/hecks/bluebook/dsl/context_map_builder.rb +73 -0
- data/lib/hecks/bluebook/dsl/entity_builder/rules.rb +17 -17
- data/lib/hecks/bluebook/dsl/entity_builder/scope.rb +3 -3
- data/lib/hecks/bluebook/dsl/entity_builder/sealing.rb +2 -2
- data/lib/hecks/bluebook/dsl/entity_builder.rb +3 -3
- data/lib/hecks/bluebook/dsl/hecksagon_builder/{chapter_scope.rb → bluebook_scope.rb} +14 -14
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +33 -27
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +6 -2
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +4 -1
- data/lib/hecks/bluebook/dsl/query_builder.rb +14 -8
- data/lib/hecks/bluebook/dsl/read_model_builder/sealing.rb +6 -11
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +1 -2
- data/lib/hecks/bluebook/dsl/word_gate.rb +2 -1
- data/lib/hecks/bluebook/dsl/world_builder.rb +2 -2
- data/lib/hecks/bluebook/dsl.rb +3 -2
- data/lib/hecks/bluebook/expression/ast_json/emitters.rb +4 -0
- data/lib/hecks/bluebook/expression/ast_json.rb +5 -3
- data/lib/hecks/bluebook/expression/ast_reader/blocks.rb +41 -0
- data/lib/hecks/bluebook/expression/ast_reader.rb +2 -10
- data/lib/hecks/bluebook/expression/canonical_form.rb +9 -4
- data/lib/hecks/bluebook/expression/evaluator/parser.rb +4 -1
- data/lib/hecks/bluebook/expression/evaluator.rb +13 -9
- data/lib/hecks/bluebook/expression/projection.json +6 -0
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +50 -2
- data/lib/hecks/bluebook/expression/resolver/lookups.rb +2 -1
- data/lib/hecks/bluebook/expression/resolver/operations.rb +1 -0
- data/lib/hecks/bluebook/expression/resolver/parsing.rb +21 -15
- data/lib/hecks/bluebook/expression/resolver/scanning.rb +4 -1
- data/lib/hecks/bluebook/expression.rb +1 -1
- data/lib/hecks/bluebook/hecksagon.rb +48 -23
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/judge/cells.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/judge/identity.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/modes.rb +27 -27
- data/lib/hecks/bluebook/meta_validator/plan.rb +5 -5
- data/lib/hecks/bluebook/meta_validator/readings/fields.rb +3 -3
- data/lib/hecks/bluebook/meta_validator/reconstruction/heads.rb +11 -3
- data/lib/hecks/bluebook/meta_validator/reconstruction/reading.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +19 -19
- data/lib/hecks/bluebook/meta_validator/shapes.rb +6 -1
- data/lib/hecks/bluebook/meta_validator/syntax_boot/disk_cache.rb +18 -15
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +21 -15
- data/lib/hecks/bluebook/meta_validator/verdict_cache/prebuilt.rb +4 -1
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +11 -10
- data/lib/hecks/bluebook/meta_validator.rb +25 -22
- data/lib/hecks/bluebook/model_check/aggregate_checks.rb +68 -0
- data/lib/hecks/bluebook/model_check/ask_checks.rb +6 -5
- data/lib/hecks/bluebook/model_check/client_profile.rb +8 -42
- data/lib/hecks/bluebook/model_check/context_checks.rb +97 -0
- data/lib/hecks/bluebook/model_check/context_map_checks.rb +158 -0
- data/lib/hecks/bluebook/model_check/lifecycle_checks.rb +27 -5
- data/lib/hecks/bluebook/model_check/one_of_checks.rb +106 -0
- data/lib/hecks/bluebook/model_check/orphan_checks.rb +129 -0
- data/lib/hecks/bluebook/model_check/redundant_relationships.rb +38 -0
- data/lib/hecks/bluebook/model_check/reference_checks.rb +11 -1
- data/lib/hecks/bluebook/model_check/saga_checks.rb +8 -3
- data/lib/hecks/bluebook/model_check/verbs.rb +4 -4
- data/lib/hecks/bluebook/model_check.rb +32 -12
- data/lib/hecks/bluebook/named_pattern.rb +12 -1
- data/lib/hecks/bluebook/one_of_union.rb +35 -0
- data/lib/hecks/bluebook/pattern_subset.rb +12 -3
- data/lib/hecks/bluebook/project_loader.rb +3 -2
- data/lib/hecks/bluebook/project_register.rb +21 -5
- data/lib/hecks/bluebook/query.rb +4 -1
- data/lib/hecks/bluebook/read_model.rb +5 -5
- data/lib/hecks/bluebook/reference.rb +3 -3
- data/lib/hecks/bluebook/smoke_test.rb +15 -13
- data/lib/hecks/bluebook/synthesizer.rb +48 -13
- data/lib/hecks/bluebook/translation_expression/parser.rb +14 -5
- data/lib/hecks/bluebook.rb +6 -3
- data/lib/hecks/bulk/bluebook/adapters/row_sweeper.adapter +7 -0
- data/lib/hecks/bulk/bluebook/bulk.behaviors +18 -0
- data/lib/hecks/bulk/bluebook/bulk.bluebook +126 -0
- data/lib/hecks/bulk/bluebook/bulk.hecksagon +11 -0
- data/lib/hecks/bulk/bluebook/bulk.ports.hecksagon +13 -0
- data/lib/hecks/bulk/plan.rb +60 -0
- data/lib/hecks/bulk/row_sweeper.rb +34 -0
- data/lib/hecks/bulk/set_arguments.rb +68 -0
- data/lib/hecks/bulk/set_eligibility.rb +50 -0
- data/lib/hecks/bulk/set_planner.rb +100 -0
- data/lib/hecks/bulk/set_sweep.rb +78 -0
- data/lib/hecks/bulk/sweep.rb +67 -0
- data/lib/hecks/cli/check_era.rb +6 -6
- data/lib/hecks/cli/command_table.rb +2 -2
- data/lib/hecks/cli/console.rb +11 -6
- data/lib/hecks/cli/document.rb +14 -9
- data/lib/hecks/cli/domain_writer.rb +4 -1
- data/lib/hecks/cli/expected_era.rb +139 -0
- data/lib/hecks/cli/expression_projection.rb +2 -2
- data/lib/hecks/cli/follow.rb +3 -3
- data/lib/hecks/cli/history.rb +4 -1
- data/lib/hecks/cli/interview_draft.rb +8 -1
- data/lib/hecks/cli/interview_record.rb +7 -3
- data/lib/hecks/cli/interview_run.rb +7 -5
- data/lib/hecks/cli/interview_session/proposals.rb +5 -2
- data/lib/hecks/cli/interview_session.rb +3 -2
- data/lib/hecks/cli/interview_shape/lifecycle.rb +92 -0
- data/lib/hecks/cli/interview_shape.rb +19 -73
- data/lib/hecks/cli/model_check/examination.rb +63 -14
- data/lib/hecks/cli/model_check/sources.rb +4 -2
- data/lib/hecks/cli/model_check.rb +1 -1
- data/lib/hecks/cli/persistence_notice.rb +45 -0
- data/lib/hecks/cli/present.rb +1 -1
- data/lib/hecks/cli/project_cli/launcher_source.rb +11 -9
- data/lib/hecks/cli/project_cli/templates.rb +24 -3
- data/lib/hecks/cli/project_cli.rb +11 -13
- data/lib/hecks/cli/project_diagrams.rb +20 -20
- data/lib/hecks/cli/project_glossary.rb +14 -14
- data/lib/hecks/cli/reference.rb +1 -1
- data/lib/hecks/cli/refresh_projections.rb +16 -11
- data/lib/hecks/cli/run.rb +27 -5
- data/lib/hecks/cli/seed_semantics_corpus.rb +14 -4
- data/lib/hecks/cli/shape.rb +3 -4
- data/lib/hecks/cli/smoke_http/transport.rb +3 -2
- data/lib/hecks/cli/statements.rb +7 -7
- data/lib/hecks/cli/stores.rb +70 -2
- data/lib/hecks/cli/stress_concurrency_specs/reporting.rb +4 -1
- data/lib/hecks/codemod.rb +3 -3
- data/lib/hecks/composer/adapters/claude_composer.rb +3 -2
- data/lib/hecks/composer/agent.rb +1 -1
- data/lib/hecks/composer/assistant.rb +2 -2
- data/lib/hecks/composer/behavior_run.rb +7 -1
- data/lib/hecks/composer/bluebook/composer.bluebook +18 -25
- data/lib/hecks/composer/bluebook/composer.hecksagon +1 -1
- data/lib/hecks/composer/board.rb +6 -6
- data/lib/hecks/composer/board_edit/command_text.rb +4 -1
- data/lib/hecks/composer/board_edit/source.rb +18 -9
- data/lib/hecks/composer/board_edit.rb +2 -2
- data/lib/hecks/composer/check.rb +22 -22
- data/lib/hecks/composer/domains.rb +1 -1
- data/lib/hecks/composer/live.rb +19 -13
- data/lib/hecks/composer/outline.rb +4 -1
- data/lib/hecks/composer/page.html +1 -1
- data/lib/hecks/composer/panels.rb +6 -3
- data/lib/hecks/composer/parse.rb +53 -36
- data/lib/hecks/composer/session.rb +22 -16
- data/lib/hecks/composer/toolbox.rb +9 -6
- data/lib/hecks/composer/web.rb +3 -2
- data/lib/hecks/composer/workbench.rb +12 -9
- data/lib/hecks/composer.rb +1 -1
- data/lib/hecks/construct.rb +7 -7
- data/lib/hecks/corpus/adapters.rb +2 -1
- data/lib/hecks/corpus/routes.rb +25 -5
- data/lib/hecks/corpus/rust.rb +59 -24
- data/lib/hecks/corpus/sources.rb +12 -11
- data/lib/hecks/corpus/worlds.rb +2 -1
- data/lib/hecks/corpus.rb +30 -22
- data/lib/hecks/deploy/bluebook/deploy.bluebook +304 -518
- data/lib/hecks/doc/reference/harvester.rb +4 -1
- data/lib/hecks/doc/reference/readme.rb +11 -6
- data/lib/hecks/doc/reference/rendering.rb +11 -5
- data/lib/hecks/doc/reference.rb +7 -5
- data/lib/hecks/embryonaut_bluebook/lock.rb +5 -1
- data/lib/hecks/embryonaut_bluebook/manifest.rb +7 -1
- data/lib/hecks/embryonaut_bluebook/registry/changelog.rb +4 -1
- data/lib/hecks/embryonaut_bluebook/registry/versions.rb +13 -2
- data/lib/hecks/embryonaut_bluebook/registry.rb +5 -2
- data/lib/hecks/embryonaut_bluebook/shape.rb +1 -1
- data/lib/hecks/embryonaut_bluebook/vendor.rb +5 -2
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +15 -1
- data/lib/hecks/embryonaut_bluebook.rb +26 -12
- data/lib/hecks/engine_agreement.rb +4 -1
- data/lib/hecks/forms/app/commands.rb +35 -2
- data/lib/hecks/forms/app/records.rb +12 -12
- data/lib/hecks/forms/app/responses.rb +1 -1
- data/lib/hecks/forms/app.rb +22 -19
- data/lib/hecks/forms/command_form_renderer.rb +2 -2
- data/lib/hecks/forms/field_controls.rb +6 -1
- data/lib/hecks/forms/field_shape.rb +14 -6
- data/lib/hecks/forms/html.rb +8 -7
- data/lib/hecks/forms/html_pattern.rb +80 -0
- data/lib/hecks/forms/index_renderer.rb +11 -11
- data/lib/hecks/forms/params.rb +2 -1
- data/lib/hecks/forms/port_argument.rb +4 -1
- data/lib/hecks/forms/query_form_renderer.rb +1 -1
- data/lib/hecks/forms/record_renderer.rb +2 -2
- data/lib/hecks/forms/record_table.rb +1 -1
- data/lib/hecks/forms/reference_options.rb +2 -1
- data/lib/hecks/forms/text_shape.rb +4 -1
- data/lib/hecks/forms/value_object_shape.rb +1 -1
- data/lib/hecks/forms.rb +7 -7
- data/lib/hecks/fqn.rb +8 -2
- data/lib/hecks/framework/bluebook/change.bluebook +10 -15
- data/lib/hecks/framework/bluebook/change.hecksagon +1 -1
- data/lib/hecks/framework/bluebook/compliance.bluebook +27 -126
- data/lib/hecks/framework/bluebook/console_settings.bluebook +199 -114
- data/lib/hecks/framework/bluebook/framework.hecksagon +7 -0
- data/lib/hecks/framework/bluebook/governance.bluebook +36 -14
- data/lib/hecks/framework/bluebook/identity.bluebook +1 -1
- data/lib/hecks/framework/bluebook/privacy.bluebook +123 -19
- data/lib/hecks/framework/bluebook/privacy.key_keeper.adapter +7 -0
- data/lib/hecks/framework/bluebook/privacy.ports.hecksagon +19 -0
- data/lib/hecks/framework/compliance.oidc.json +6 -36
- data/lib/hecks/framework/console_settings.oidc.json +39 -24
- data/lib/hecks/framework/privacy.oidc.json +25 -0
- data/lib/hecks/framework.rb +24 -8
- data/lib/hecks/framework_bluebooks.rb +156 -0
- data/lib/hecks/freezer.rb +7 -1
- data/lib/hecks/fuzzing/combination_miner.rb +9 -6
- data/lib/hecks/fuzzing/concurrent_dispatch/world_copy.rb +3 -1
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +2 -2
- data/lib/hecks/fuzzing/domain_generator.rb +8 -3
- data/lib/hecks/fuzzing/era_boundary.rb +1 -1
- data/lib/hecks/fuzzing/failure_triage.rb +13 -6
- data/lib/hecks/fuzzing/form_census/{chapter_forms.rb → bluebook_forms.rb} +12 -12
- data/lib/hecks/fuzzing/form_census.rb +14 -12
- data/lib/hecks/fuzzing/generated_domain_check.rb +2 -1
- data/lib/hecks/fuzzing/isolated_boot/postgres_scratch.rb +5 -5
- data/lib/hecks/fuzzing/isolated_boot/scratch_schema.rb +19 -9
- data/lib/hecks/fuzzing/isolated_boot/worlds.rb +13 -8
- data/lib/hecks/fuzzing/isolated_boot.rb +4 -5
- data/lib/hecks/fuzzing/mutation/checks.rb +2 -1
- data/lib/hecks/fuzzing/mutation/finders.rb +25 -8
- data/lib/hecks/fuzzing/mutation/observation.rb +3 -1
- data/lib/hecks/fuzzing/mutation/source_map.rb +3 -1
- data/lib/hecks/fuzzing/nondeterministic.rb +10 -4
- data/lib/hecks/fuzzing/outside_world.rb +1 -1
- data/lib/hecks/fuzzing/projection_agreement/binding.rb +63 -0
- data/lib/hecks/fuzzing/projection_agreement/comparison.rb +104 -0
- data/lib/hecks/fuzzing/projection_agreement.rb +68 -0
- data/lib/hecks/fuzzing/properties/aggregation_recompute.rb +1 -1
- data/lib/hecks/fuzzing/properties/bulk_parity.rb +118 -0
- data/lib/hecks/fuzzing/properties/catalog.rb +7 -0
- data/lib/hecks/fuzzing/properties/cursor_walks.rb +56 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +39 -1
- data/lib/hecks/fuzzing/properties/group_by_recompute.rb +6 -2
- data/lib/hecks/fuzzing/properties/grouped_reduction_recompute.rb +88 -0
- data/lib/hecks/fuzzing/properties/guards.rb +4 -1
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +12 -2
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +2 -1
- data/lib/hecks/fuzzing/properties/mutation_recompute.rb +3 -2
- data/lib/hecks/fuzzing/properties/piece_invariants.rb +3 -2
- data/lib/hecks/fuzzing/properties/query_rows.rb +5 -3
- data/lib/hecks/fuzzing/properties/querying.rb +6 -3
- data/lib/hecks/fuzzing/properties/read_model_names.rb +8 -2
- data/lib/hecks/fuzzing/properties/read_models.rb +2 -1
- data/lib/hecks/fuzzing/properties/refusals.rb +35 -0
- data/lib/hecks/fuzzing/properties/role_checks.rb +3 -2
- data/lib/hecks/fuzzing/properties/set_transform_parity.rb +73 -0
- data/lib/hecks/fuzzing/properties/tenant_scope.rb +3 -2
- data/lib/hecks/fuzzing/properties.rb +6 -0
- data/lib/hecks/fuzzing/qa_settings.rb +4 -4
- data/lib/hecks/fuzzing/replay/fan_out.rb +4 -3
- data/lib/hecks/fuzzing/replay/filters.rb +2 -2
- data/lib/hecks/fuzzing/replay/guard_check.rb +2 -2
- data/lib/hecks/fuzzing/replay/mutation_trace.rb +23 -10
- data/lib/hecks/fuzzing/replay/observation.rb +11 -7
- data/lib/hecks/fuzzing/replay/read_only_steps.rb +40 -3
- data/lib/hecks/fuzzing/replay/refusal_trace.rb +44 -0
- data/lib/hecks/fuzzing/replay/role_check.rb +2 -2
- data/lib/hecks/fuzzing/replay/session.rb +38 -18
- data/lib/hecks/fuzzing/replay/validation_reports.rb +61 -0
- data/lib/hecks/fuzzing/replay.rb +2 -2
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +3 -3
- data/lib/hecks/fuzzing/self_consistency/saga_redelivery.rb +18 -8
- data/lib/hecks/fuzzing/self_consistency/sagas.rb +2 -2
- data/lib/hecks/fuzzing/self_consistency/value_objects.rb +9 -2
- data/lib/hecks/fuzzing/self_consistency.rb +4 -8
- data/lib/hecks/fuzzing/sequence_generator/adversary/argument_mutations.rb +15 -5
- data/lib/hecks/fuzzing/sequence_generator/adversary/caller_draw.rb +14 -10
- data/lib/hecks/fuzzing/sequence_generator/adversary/precedence_mutations.rb +1 -1
- data/lib/hecks/fuzzing/sequence_generator/argument_drawing.rb +2 -1
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +21 -13
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +1 -1
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +3 -3
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +2 -2
- data/lib/hecks/fuzzing/sequence_generator.rb +1 -2
- data/lib/hecks/fuzzing/shrinker.rb +4 -1
- data/lib/hecks/fuzzing/value_generator.rb +14 -8
- data/lib/hecks/fuzzing.rb +1 -0
- data/lib/hecks/grammar/dialects.rb +56 -0
- data/lib/hecks/grammar/evolve/arguments.rb +1 -1
- data/lib/hecks/grammar/evolve/keywords.rb +1 -1
- data/lib/hecks/grammar/evolve/seeds.rb +34 -8
- data/lib/hecks/grammar/expression.bluebook +3 -0
- data/lib/hecks/grammar/expression_operators.json +1071 -1
- data/lib/hecks/grammar/operators.rb +45 -22
- data/lib/hecks/grammar/translation.bluebook +2 -0
- data/lib/hecks/grammar/translation_ledger.rb +45 -0
- data/lib/hecks/grammar/translation_rules.json +646 -0
- data/lib/hecks/grammar.rb +27 -23
- data/lib/hecks/hecks/adapters/claude_agents.adapter +7 -0
- data/lib/hecks/hecks/adapters/claude_agents.rb +119 -0
- data/lib/hecks/hecks/adapters/claude_transcript.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/cache_clearing.rb +32 -0
- data/lib/hecks/hecks/adapters/codebase/cargo_tests.rb +50 -0
- data/lib/hecks/hecks/adapters/codebase/composing.rb +3 -2
- data/lib/hecks/hecks/adapters/codebase/conformance.rb +17 -1
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +1 -1
- data/lib/hecks/hecks/adapters/codebase/declarations.rb +144 -0
- data/lib/hecks/hecks/adapters/codebase/inspection.rb +5 -4
- data/lib/hecks/hecks/adapters/codebase/language.rb +1 -0
- data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +7 -1
- data/lib/hecks/hecks/adapters/codebase/previewing.rb +62 -0
- data/lib/hecks/hecks/adapters/codebase/promotion/gate.rb +1 -1
- data/lib/hecks/hecks/adapters/codebase/reading.rb +94 -0
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +6 -2
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +1 -1
- data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +5 -2
- data/lib/hecks/hecks/adapters/codebase/search.rb +91 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +37 -2
- data/lib/hecks/hecks/adapters/codebase/static_directory.rb +59 -0
- data/lib/hecks/hecks/adapters/codebase/style.rb +2 -2
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +30 -11
- data/lib/hecks/hecks/adapters/codebase/test_suite.rb +41 -3
- data/lib/hecks/hecks/adapters/console_capture.rb +27 -0
- data/lib/hecks/hecks/adapters/deploy_plans.rb +12 -3
- data/lib/hecks/hecks/adapters/deploy_toolchain/answers.rb +14 -5
- data/lib/hecks/hecks/adapters/deploy_toolchain/scripts.rb +6 -3
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +3 -3
- data/lib/hecks/hecks/adapters/desktop_alert/alert.rb +50 -0
- data/lib/hecks/hecks/adapters/desktop_alert.adapter +7 -0
- data/lib/hecks/hecks/adapters/desktop_alert.rb +99 -0
- data/lib/hecks/hecks/adapters/finding_github.rb +4 -1
- data/lib/hecks/hecks/adapters/first_admin.rb +16 -9
- data/lib/hecks/hecks/adapters/git.rb +5 -5
- data/lib/hecks/hecks/adapters/git_checkout.adapter +7 -0
- data/lib/hecks/hecks/adapters/git_checkout.rb +99 -0
- data/lib/hecks/hecks/adapters/git_landing/merger.rb +85 -0
- data/lib/hecks/hecks/adapters/git_landing.adapter +7 -0
- data/lib/hecks/hecks/adapters/git_landing.rb +103 -0
- data/lib/hecks/hecks/adapters/git_launcher.adapter +7 -0
- data/lib/hecks/hecks/adapters/git_launcher.rb +82 -0
- data/lib/hecks/hecks/adapters/git_refs.rb +8 -4
- data/lib/hecks/hecks/adapters/git_train/bench.rb +76 -0
- data/lib/hecks/hecks/adapters/git_train/carriage.rb +116 -0
- data/lib/hecks/hecks/adapters/git_train/local_branch.rb +69 -0
- data/lib/hecks/hecks/adapters/git_train/reading.rb +26 -0
- data/lib/hecks/hecks/adapters/git_train/survey.rb +60 -0
- data/lib/hecks/hecks/adapters/git_train.adapter +7 -0
- data/lib/hecks/hecks/adapters/git_train.rb +88 -0
- data/lib/hecks/hecks/adapters/git_worktrees.rb +81 -0
- data/lib/hecks/hecks/adapters/host_http.rb +5 -4
- data/lib/hecks/hecks/adapters/in_process_access.rb +3 -2
- data/lib/hecks/hecks/adapters/in_process_boot.rb +62 -35
- data/lib/hecks/hecks/adapters/in_process_operations.rb +30 -4
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +7 -7
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +14 -7
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +9 -7
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +3 -3
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +17 -5
- data/lib/hecks/hecks/adapters/journal_store/readings/attestation.rb +3 -3
- data/lib/hecks/hecks/adapters/journal_store/readings/scaffolding.rb +7 -4
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +2 -2
- data/lib/hecks/hecks/adapters/journal_store.rb +2 -2
- data/lib/hecks/hecks/adapters/ledger/chain.rb +63 -0
- data/lib/hecks/hecks/adapters/ledger/journal.rb +105 -0
- data/lib/hecks/hecks/adapters/ledger.adapter +7 -0
- data/lib/hecks/hecks/adapters/ledger.rb +89 -0
- data/lib/hecks/hecks/adapters/local_files.rb +15 -1
- data/lib/hecks/hecks/adapters/pg_admin.rb +7 -1
- data/lib/hecks/hecks/adapters/rails_app_files.rb +64 -0
- data/lib/hecks/hecks/adapters/rehearsal/audit_report.rb +4 -1
- data/lib/hecks/hecks/adapters/rehearsal/inquiry.rb +9 -9
- data/lib/hecks/hecks/adapters/rehearsal/memory_store.rb +2 -2
- data/lib/hecks/hecks/adapters/rehearsal/postgres_census.rb +4 -4
- data/lib/hecks/hecks/adapters/rehearsal/postgres_reading.rb +3 -3
- data/lib/hecks/hecks/adapters/rehearsal/postgres_store.rb +4 -1
- data/lib/hecks/hecks/adapters/rehearsal/rust_audit.rb +1 -1
- data/lib/hecks/hecks/adapters/rehearsal/subject.rb +34 -5
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +5 -3
- data/lib/hecks/hecks/adapters/settings_reading.adapter +7 -0
- data/lib/hecks/hecks/adapters/settings_reading.rb +142 -0
- data/lib/hecks/hecks/adapters/shell.rb +3 -0
- data/lib/hecks/hecks/adapters/site_toolchain.rb +8 -8
- data/lib/hecks/hecks/adapters/world_clearing.rb +2 -1
- data/lib/hecks/hecks/agents.bluebook +246 -0
- data/lib/hecks/hecks/codebase.behaviors +13 -1
- data/lib/hecks/hecks/codebase.bluebook +554 -586
- data/lib/hecks/hecks/composer.behaviors +10 -10
- data/lib/hecks/hecks/context_map.hecksagon +7 -6
- data/lib/hecks/hecks/custodian.behaviors +96 -1
- data/lib/hecks/hecks/custodian.bluebook +553 -157
- data/lib/hecks/hecks/deploy.behaviors +1 -1
- data/lib/hecks/hecks/deploy.world +3 -4
- data/lib/hecks/hecks/hecks.behaviors +2 -2
- data/lib/hecks/hecks/hecks.bluebook +9 -14
- data/lib/hecks/hecks/hecks.contextmap +25 -0
- data/lib/hecks/hecks/hecks.hecksagon +139 -6
- data/lib/hecks/hecks/hecks.world +22 -21
- data/lib/hecks/hecks/ledger.bluebook +190 -0
- data/lib/hecks/hecks/rehearsal.bluebook +43 -77
- data/lib/hecks/hecks/shapes.json +17 -0
- data/lib/hecks/hecks/site.behaviors +1 -1
- data/lib/hecks/hecks/site.world +3 -3
- data/lib/hecks/hecks/tooling.bluebook +170 -174
- data/lib/hecks/hecks/translations/2-148748.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-15a2e4.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-1b447c.bluebook +5 -0
- data/lib/hecks/hecks/translations/2-240474.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-44446d.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-46ca26.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-473eab.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-75cc28.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-781c07.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-84c18b.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-aa5fff.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-bfdb48.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-cd96d8.bluebook +10 -0
- data/lib/hecks/hecks/translations/2-d43f71.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-eea577.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-f53248.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-fbb677.bluebook +2 -0
- data/lib/hecks/hecks/translations/2-ffdf12.bluebook +6 -0
- data/lib/hecks/ir.rb +8 -1
- data/lib/hecks/kernel_capabilities/templates.rb +4 -3
- data/lib/hecks/kernel_capabilities.rb +6 -6
- data/lib/hecks/language/adapter.bluebook +61 -0
- data/lib/hecks/language/bluebook/aggregate.bluebook +36 -6
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +1 -1
- data/lib/hecks/language/bluebook/bluebook.behaviors +6 -6
- data/lib/hecks/language/bluebook/bluebook.bluebook +28 -51
- data/lib/hecks/language/bluebook/bluebook.hecksagon +1 -1
- data/lib/hecks/language/bluebook/command.bluebook +34 -2
- data/lib/hecks/language/bluebook/entity.bluebook +2 -2
- data/lib/hecks/language/bluebook/policy.behaviors +2 -2
- data/lib/hecks/language/bluebook/policy.bluebook +3 -3
- data/lib/hecks/language/bluebook/process_manager.bluebook +3 -3
- data/lib/hecks/language/bluebook/projection.bluebook +11 -10
- data/lib/hecks/language/bluebook/query.bluebook +1 -0
- data/lib/hecks/language/bluebook/shape.bluebook +1 -1
- data/lib/hecks/language/bluebook/syntax.bluebook +3 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +287 -6
- data/lib/hecks/language/context_map/context_map.bluebook +90 -0
- data/lib/hecks/language/hecksagon/domain_port.bluebook +2 -2
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +8 -8
- data/lib/hecks/language/oidc.json +10 -0
- data/lib/hecks/language/port.bluebook +1 -1
- data/lib/hecks/language/sentence/sentence.bluebook +210 -0
- data/lib/hecks/language/world/world.bluebook +6 -6
- data/lib/hecks/literal.rb +8 -2
- data/lib/hecks/naming.rb +44 -14
- data/lib/hecks/persistence_legacy_fixture/postgres_stores.rb +7 -6
- data/lib/hecks/persistence_legacy_fixture/regenerate.rb +6 -2
- data/lib/hecks/ports/access_control.rb +2 -2
- data/lib/hecks/ports/agent/answers.rb +2 -2
- data/lib/hecks/ports/agent.rb +2 -2
- data/lib/hecks/ports/authentication.port +6 -0
- data/lib/hecks/ports/authentication.rb +67 -0
- data/lib/hecks/ports/authorization.rb +3 -3
- data/lib/hecks/ports/dispatch.port +5 -0
- data/lib/hecks/ports/dispatch.rb +28 -0
- data/lib/hecks/ports/key_vault.rb +9 -6
- data/lib/hecks/ports/loading.rb +23 -7
- data/lib/hecks/ports/persistence/append_only.rb +18 -1
- data/lib/hecks/ports/persistence/binding_policy.rb +70 -11
- data/lib/hecks/ports/persistence/codec_boundary.rb +3 -2
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +10 -10
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +4 -5
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +11 -11
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +6 -6
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +32 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +0 -1
- data/lib/hecks/ports/persistence/remote_runtime.rb +3 -2
- data/lib/hecks/ports/persistence/set_statement.rb +29 -0
- data/lib/hecks/ports/persistence/set_transforming.rb +30 -0
- data/lib/hecks/ports/persistence/state_codec.rb +3 -3
- data/lib/hecks/ports/persistence.rb +0 -4
- data/lib/hecks/ports/projection.rb +4 -1
- data/lib/hecks/ports/query/cursor.rb +131 -0
- data/lib/hecks/ports/query/in_memory.rb +17 -10
- data/lib/hecks/ports/query.rb +4 -2
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projection_files/expression_tables.rb +3 -3
- data/lib/hecks/projection_files/sentence_table.rb +17 -0
- data/lib/hecks/projection_files.rb +16 -13
- data/lib/hecks/projections/bootstrap_table.rb +3 -3
- data/lib/hecks/projections/command_schema/shape.rb +95 -0
- data/lib/hecks/projections/command_schema.rb +74 -0
- data/lib/hecks/projections/context_map_diagram.rb +135 -0
- data/lib/hecks/projections/context_relationships.rb +125 -0
- data/lib/hecks/projections/deploy/box/access.rb +7 -4
- data/lib/hecks/projections/deploy/box/caddy.rb +2 -1
- data/lib/hecks/projections/deploy/box/compose.rb +4 -8
- data/lib/hecks/projections/deploy/box/edge.rb +2 -1
- data/lib/hecks/projections/deploy/box/hosting.rb +4 -2
- data/lib/hecks/projections/deploy/box/hosting_parts.rb +27 -12
- data/lib/hecks/projections/deploy/box/logging.rb +2 -3
- data/lib/hecks/projections/deploy/box/roll_recipe.rb +1 -3
- data/lib/hecks/projections/deploy/box/roll_scripts.rb +1 -16
- data/lib/hecks/projections/deploy/box/settings/containers.rb +29 -15
- data/lib/hecks/projections/deploy/box/settings/patterns.rb +5 -6
- data/lib/hecks/projections/deploy/box/settings/types.rb +5 -10
- data/lib/hecks/projections/deploy/box/settings.rb +9 -8
- data/lib/hecks/projections/deploy/box/stack_yaml.rb +10 -14
- data/lib/hecks/projections/deploy/box/templates/box.yaml.tmpl +0 -1
- data/lib/hecks/projections/deploy/box/templates/deploy-service.sh.tmpl +16 -3
- data/lib/hecks/projections/deploy/box/templates/hosting.mk.tmpl +7 -6
- data/lib/hecks/projections/deploy/box/templates/render-compose-taskdef.sh.tmpl +2 -6
- data/lib/hecks/projections/deploy/box/templates/render-compose.sh.tmpl +2 -6
- data/lib/hecks/projections/deploy/box.rb +16 -9
- data/lib/hecks/projections/deploy/fargate/assembly.rb +7 -2
- data/lib/hecks/projections/deploy/fargate/cdn/options.rb +4 -1
- data/lib/hecks/projections/deploy/fargate/check.rb +3 -1
- data/lib/hecks/projections/deploy/fargate/containers/rendering.rb +8 -5
- data/lib/hecks/projections/deploy/fargate/containers/routing.rb +2 -1
- data/lib/hecks/projections/deploy/fargate/monitoring/reading.rb +4 -1
- data/lib/hecks/projections/deploy/fargate/sections.rb +7 -4
- data/lib/hecks/projections/deploy/fargate/settings/ids.rb +4 -1
- data/lib/hecks/projections/deploy/fargate/settings/tuning.rb +13 -4
- data/lib/hecks/projections/deploy/fargate/stack/blocks.rb +6 -1
- data/lib/hecks/projections/deploy/fargate/stack/readers.rb +2 -1
- data/lib/hecks/projections/deploy/fargate/stack.rb +11 -6
- data/lib/hecks/projections/deploy/fargate/yaml.rb +10 -3
- data/lib/hecks/projections/deploy/fargate.rb +8 -3
- data/lib/hecks/projections/deploy/lambda/stack/blocks.rb +4 -2
- data/lib/hecks/projections/deploy/lambda/stack/deploy_chain.rb +2 -1
- data/lib/hecks/projections/deploy/lambda/stack/reading.rb +12 -9
- data/lib/hecks/projections/deploy/lambda/stack/reading_web.rb +7 -2
- data/lib/hecks/projections/deploy/lambda/stack/web.rb +2 -1
- data/lib/hecks/projections/deploy/lambda.rb +8 -3
- data/lib/hecks/projections/deploy/preview/containers.rb +14 -1
- data/lib/hecks/projections/deploy/preview/preview.sh.erb +2 -2
- data/lib/hecks/projections/deploy/preview/routing_yaml.rb +8 -6
- data/lib/hecks/projections/deploy/preview/settings.rb +23 -5
- data/lib/hecks/projections/deploy/preview/task_yaml.rb +1 -1
- data/lib/hecks/projections/deploy/preview/yaml_text.rb +2 -1
- data/lib/hecks/projections/deploy/scripts/settings.rb +5 -2
- data/lib/hecks/projections/deploy/shared.rb +12 -1
- data/lib/hecks/projections/deploy/shared_database_instance.rb +1 -1
- data/lib/hecks/projections/deploy/smoke.rb +3 -1
- data/lib/hecks/projections/deploy/template_diff/normalizer.rb +4 -1
- data/lib/hecks/projections/deploy/templates/lambda/mint_era_shared.tmpl +2 -2
- data/lib/hecks/projections/deploy/templates/lambda/nat_comment_oauth.tmpl +1 -1
- data/lib/hecks/projections/deploy/vercel.rb +1 -1
- data/lib/hecks/projections/diagrams/dispatch.rb +5 -3
- data/lib/hecks/projections/diagrams/statecharts.rb +8 -3
- data/lib/hecks/projections/diagrams/structure.rb +9 -6
- data/lib/hecks/projections/diagrams.rb +9 -7
- data/lib/hecks/projections/glossary/entries.rb +4 -4
- data/lib/hecks/projections/glossary/html/page.rb +4 -1
- data/lib/hecks/projections/glossary/html/parser.rb +13 -4
- data/lib/hecks/projections/glossary/html/sections.rb +4 -1
- data/lib/hecks/projections/glossary/html.rb +10 -3
- data/lib/hecks/projections/glossary/markdown.rb +4 -3
- data/lib/hecks/projections/glossary/mermaid.rb +8 -5
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sensitivity.rb +11 -4
- data/lib/hecks/projections/glossary/sentences/actions.rb +5 -2
- data/lib/hecks/projections/glossary/sentences/queries.rb +2 -1
- data/lib/hecks/projections/glossary/sentences/value_objects.rb +15 -6
- data/lib/hecks/projections/glossary/sentences.rb +6 -3
- data/lib/hecks/projections/glossary/slugs.rb +8 -1
- data/lib/hecks/projections/glossary.rb +3 -3
- data/lib/hecks/projections/model.rb +10 -6
- data/lib/hecks/projections/oidc.rb +19 -7
- data/lib/hecks/projections/parser_table.rb +10 -10
- data/lib/hecks/projections/phrase_table.rb +105 -0
- data/lib/hecks/projections/plain_english.rb +14 -4
- data/lib/hecks/projections/rails_app.rb +77 -0
- data/lib/hecks/projections/reference.rb +2 -2
- data/lib/hecks/projections/rust_vocabulary/arguments.rb +1 -1
- data/lib/hecks/projections/rust_vocabulary/tables.rb +20 -0
- data/lib/hecks/projections/rust_vocabulary.rb +14 -17
- data/lib/hecks/projections/site/admin.rb +7 -7
- data/lib/hecks/projections/site/admin_cms.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/blocks.rb +5 -2
- data/lib/hecks/projections/site/cms_editor/checks.rb +9 -6
- data/lib/hecks/projections/site/cms_editor/color.rb +4 -1
- data/lib/hecks/projections/site/cms_editor/composition.rb +20 -18
- data/lib/hecks/projections/site/cms_editor/destructive.rb +4 -1
- data/lib/hecks/projections/site/cms_editor/drafts.rb +6 -2
- data/lib/hecks/projections/site/cms_editor/key_kinds.rb +4 -1
- data/lib/hecks/projections/site/cms_editor/limits.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/media.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/moment.rb +3 -1
- data/lib/hecks/projections/site/cms_editor/pickers.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/preview.rb +10 -4
- data/lib/hecks/projections/site/cms_editor/relations.rb +3 -2
- data/lib/hecks/projections/site/cms_editor/scale_checks.rb +32 -25
- data/lib/hecks/projections/site/cms_editor/scheduling.rb +1 -1
- data/lib/hecks/projections/site/cms_editor/schema.rb +31 -29
- data/lib/hecks/projections/site/cms_editor/setting.rb +34 -27
- data/lib/hecks/projections/site/cms_editor/targets.rb +9 -6
- data/lib/hecks/projections/site/cms_editor/templates/src/app.ts.tmpl +3 -3
- data/lib/hecks/projections/site/cms_editor/templates/src/commands.ts.tmpl +2 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/host.ts.tmpl +1 -1
- data/lib/hecks/projections/site/cms_editor/templates/src/media/handler.ts.tmpl +4 -4
- data/lib/hecks/projections/site/cms_editor/templates/src/names.ts.tmpl +14 -14
- data/lib/hecks/projections/site/cms_editor/templates/src/schema.ts.tmpl +12 -12
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pages.ts.tmpl +12 -12
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/pickers.ts.tmpl +1 -1
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/schedule.ts.tmpl +2 -2
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/shell.ts.tmpl +11 -11
- data/lib/hecks/projections/site/cms_editor/templates/src/ui/world.ts.tmpl +3 -3
- data/lib/hecks/projections/site/cms_editor/theme_checks.rb +8 -2
- data/lib/hecks/projections/site/cms_editor.rb +19 -16
- data/lib/hecks/projections/site/edge/checks.rb +1 -1
- data/lib/hecks/projections/site/edge/function_checks.rb +1 -1
- data/lib/hecks/projections/site/edge/function_code.rb +4 -1
- data/lib/hecks/projections/site/edge/pattern.rb +4 -1
- data/lib/hecks/projections/site/edge/route_checks.rb +2 -2
- data/lib/hecks/projections/site/edge/rules.rb +1 -1
- data/lib/hecks/projections/site/edge.rb +14 -8
- data/lib/hecks/projections/site/live_edge.rb +2 -2
- data/lib/hecks/projections/site/payload/fields/definitions.rb +1 -1
- data/lib/hecks/projections/site/payload/lifecycle/text.rb +1 -1
- data/lib/hecks/projections/site/payload/model.rb +16 -12
- data/lib/hecks/projections/site/payload/specs.rb +4 -2
- data/lib/hecks/projections/site/payload_driver.rb +9 -8
- data/lib/hecks/projections/site/probe.rb +12 -3
- data/lib/hecks/projections/site/regions.rb +9 -1
- data/lib/hecks/projections/site/role_probe.rb +7 -7
- data/lib/hecks/projections/site/root_rows.rb +4 -4
- data/lib/hecks/projections/site/routes_ts/listings.rb +13 -6
- data/lib/hecks/projections/site/routes_ts.rb +6 -3
- data/lib/hecks/projections/site/site_admin.rb +1 -1
- data/lib/hecks/projections/site/site_cdn.rb +3 -2
- data/lib/hecks/projections/site/site_host.rb +1 -1
- data/lib/hecks/projections/site/site_root.rb +4 -4
- data/lib/hecks/projections/site/table/checks.rb +4 -1
- data/lib/hecks/projections/site/table/navigation_checks.rb +2 -2
- data/lib/hecks/projections/site/table/row_builder.rb +7 -7
- data/lib/hecks/projections/site/table/row_defaults.rb +1 -1
- data/lib/hecks/projections/site/table.rb +32 -26
- data/lib/hecks/projections/statements/actions.rb +4 -1
- data/lib/hecks/projections/statements/relationships.rb +15 -2
- data/lib/hecks/projections/statements.rb +2 -2
- data/lib/hecks/projections/vocabulary.rb +6 -6
- data/lib/hecks/projections/world_skeleton.rb +68 -0
- data/lib/hecks/projections.rb +5 -0
- data/lib/hecks/projector/cli_audience.rb +12 -9
- data/lib/hecks/projector/cli_projector/claims.rb +8 -8
- data/lib/hecks/projector/cli_projector/command_specs.rb +9 -5
- data/lib/hecks/projector/cli_projector/listing.rb +20 -8
- data/lib/hecks/projector/cli_projector/option_specs.rb +1 -1
- data/lib/hecks/projector/cli_projector/query_specs.rb +1 -1
- data/lib/hecks/projector/cli_projector/usage.rb +12 -8
- data/lib/hecks/projector/cli_projector.rb +2 -2
- data/lib/hecks/projector/docs_projector/behaviour.rb +5 -4
- data/lib/hecks/projector/docs_projector/closing.rb +3 -3
- data/lib/hecks/projector/docs_projector.rb +12 -7
- data/lib/hecks/projector/exporter/adapters.rb +15 -2
- data/lib/hecks/projector/exporter/bindings.rb +39 -134
- data/lib/hecks/projector/exporter/plugins.rb +55 -0
- data/lib/hecks/projector/exporter.rb +2 -0
- data/lib/hecks/projector/narrate_projector/commands.rb +4 -3
- data/lib/hecks/projector/narrate_projector/reactions.rb +2 -2
- data/lib/hecks/projector/narrate_projector.rb +9 -8
- data/lib/hecks/projector/target.rb +8 -8
- data/lib/hecks/projector.rb +9 -9
- data/lib/hecks/quality_control/adapters/agent.rb +2 -1
- data/lib/hecks/quality_control/adapters/github_checks.adapter +1 -1
- data/lib/hecks/quality_control/adapters/github_issues.adapter +1 -1
- data/lib/hecks/quality_control/adapters/github_issues.rb +6 -2
- data/lib/hecks/quality_control/adapters/target_tools.rb +9 -3
- data/lib/hecks/quality_control/cli/child.rb +1 -0
- data/lib/hecks/quality_control/cli/qa_domain_novelty/verdict.rb +1 -1
- data/lib/hecks/quality_control/cli/qa_generated_domains/evaluation.rb +12 -2
- data/lib/hecks/quality_control/cli/qa_generated_domains/promotion.rb +4 -1
- data/lib/hecks/quality_control/cli/qa_generated_domains.rb +4 -1
- data/lib/hecks/quality_control/cli/qa_log_bug/proof.rb +16 -3
- data/lib/hecks/quality_control/cli/qa_mine_combinations/booting.rb +4 -2
- data/lib/hecks/quality_control/cli/qa_mutate.rb +161 -0
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode/report.rb +4 -1
- data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +2 -1
- data/lib/hecks/quality_control/cli/qa_sweep.rb +4 -1
- data/lib/hecks/quality_control/cli/qa_tick/output.rb +28 -26
- data/lib/hecks/quality_control/cli/qa_tick/preflight.rb +12 -3
- data/lib/hecks/quality_control/cli/qa_tick.rb +24 -8
- data/lib/hecks/quality_control/quality_control.bluebook +93 -92
- data/lib/hecks/quality_control/quality_control.hecksagon +3 -3
- data/lib/hecks/quality_control/quality_control.ports.hecksagon +3 -2
- data/lib/hecks/query_ir/rules.rb +9 -9
- data/lib/hecks/query_ir/touchpoints.rb +5 -2
- data/lib/hecks/query_ir.rb +3 -3
- data/lib/hecks/query_specification/common/comparison.rb +1 -1
- data/lib/hecks/query_specification/common/dsl.rb +3 -4
- data/lib/hecks/query_specification/hop_path.rb +7 -4
- data/lib/hecks/rails_app/bluebook/rails_app.bluebook +60 -0
- data/lib/hecks/rails_app/bluebook/rails_app.hecksagon +4 -0
- data/lib/hecks/refused.rb +104 -0
- data/lib/hecks/release/approval.rb +9 -2
- data/lib/hecks/release/gem_pin.rb +7 -3
- data/lib/hecks/release/lane.rb +1 -1
- data/lib/hecks/release/runner/clean_tree.rb +4 -1
- data/lib/hecks/release/runner/console.rb +2 -1
- data/lib/hecks/router/aggregate_reader.rb +3 -4
- data/lib/hecks/router/namespace_installer.rb +31 -17
- data/lib/hecks/router.rb +2 -10
- data/lib/hecks/runtime/adapter_lookup.rb +30 -7
- data/lib/hecks/runtime/adapter_namespace.rb +21 -0
- data/lib/hecks/runtime/asking.rb +30 -0
- data/lib/hecks/runtime/caller.rb +22 -0
- data/lib/hecks/runtime/capability_graph.rb +2 -1
- data/lib/hecks/runtime/collecting.rb +86 -0
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +13 -8
- data/lib/hecks/runtime/command_interpreter/delegation.rb +46 -17
- data/lib/hecks/runtime/command_interpreter/hydration.rb +17 -21
- data/lib/hecks/runtime/command_interpreter/list_mutations.rb +2 -1
- data/lib/hecks/runtime/command_interpreter/persisting.rb +13 -12
- data/lib/hecks/runtime/command_interpreter.rb +17 -10
- data/lib/hecks/runtime/command_rules/admissibility.rb +17 -10
- data/lib/hecks/runtime/command_rules/authorization.rb +8 -6
- data/lib/hecks/runtime/command_rules/guard_state.rb +10 -10
- data/lib/hecks/runtime/command_rules/invariants.rb +3 -1
- data/lib/hecks/runtime/command_rules/missing_reference.rb +25 -0
- data/lib/hecks/runtime/command_rules/one_of_references.rb +44 -0
- data/lib/hecks/runtime/command_rules/references.rb +14 -14
- data/lib/hecks/runtime/command_rules.rb +2 -0
- data/lib/hecks/runtime/declarations.rb +66 -0
- data/lib/hecks/runtime/dependency_planning/analyzer.rb +2 -1
- data/lib/hecks/runtime/dependency_planning/expression_reads.rb +3 -2
- data/lib/hecks/runtime/dependency_planning/mutation_analysis.rb +2 -1
- data/lib/hecks/runtime/dispatch_claim.rb +72 -0
- data/lib/hecks/runtime/dispatch_context.rb +41 -0
- data/lib/hecks/runtime/dispatcher/queries.rb +17 -0
- data/lib/hecks/runtime/dispatcher/routing.rb +34 -12
- data/lib/hecks/runtime/dispatcher/validating.rb +73 -0
- data/lib/hecks/runtime/dispatcher.rb +39 -23
- data/lib/hecks/runtime/entity_element/location.rb +15 -7
- data/lib/hecks/runtime/entity_element/mutations.rb +4 -1
- data/lib/hecks/runtime/entity_interpreter/enforcement.rb +1 -1
- data/lib/hecks/runtime/entity_interpreter/locating.rb +1 -1
- data/lib/hecks/runtime/entity_interpreter/saving.rb +5 -5
- data/lib/hecks/runtime/entity_interpreter.rb +11 -4
- data/lib/hecks/runtime/errors.rb +30 -17
- data/lib/hecks/runtime/identity.rb +9 -0
- data/lib/hecks/runtime/instance.rb +3 -2
- data/lib/hecks/runtime/interpreting.rb +63 -6
- data/lib/hecks/runtime/invocation_builder.rb +7 -4
- data/lib/hecks/runtime/invocation_facts.rb +13 -8
- data/lib/hecks/runtime/loader/boot_steps.rb +3 -2
- data/lib/hecks/runtime/loader/described.rb +4 -0
- data/lib/hecks/runtime/loader.rb +52 -62
- data/lib/hecks/runtime/outbox/redrive.rb +105 -0
- data/lib/hecks/runtime/outbox/relay.rb +11 -39
- data/lib/hecks/runtime/outbox/runnable.rb +60 -0
- data/lib/hecks/runtime/outbox.rb +15 -1
- data/lib/hecks/runtime/policy_interpreter/arguments.rb +7 -1
- data/lib/hecks/runtime/policy_interpreter/fan_out.rb +8 -35
- data/lib/hecks/runtime/policy_interpreter.rb +8 -0
- data/lib/hecks/runtime/port_operation_interpreter.rb +27 -13
- data/lib/hecks/runtime/query_interpreter/outside_answer.rb +1 -1
- data/lib/hecks/runtime/query_interpreter/paging.rb +42 -0
- data/lib/hecks/runtime/query_interpreter/plugin_answer.rb +54 -0
- data/lib/hecks/runtime/query_interpreter/store_specification.rb +32 -0
- data/lib/hecks/runtime/query_interpreter.rb +16 -9
- data/lib/hecks/runtime/reaction_invocation/target_resolution.rb +10 -4
- data/lib/hecks/runtime/reaction_invocation.rb +5 -2
- data/lib/hecks/runtime/read_model_interpreter/head_ordering.rb +1 -1
- data/lib/hecks/runtime/read_model_interpreter/reductions.rb +23 -15
- data/lib/hecks/runtime/read_model_interpreter/shaping.rb +18 -7
- data/lib/hecks/runtime/read_model_interpreter.rb +5 -2
- data/lib/hecks/runtime/reference_hop.rb +3 -2
- data/lib/hecks/runtime/refusal_wording.rb +15 -6
- data/lib/hecks/runtime/registry/adapter_capabilities.rb +43 -0
- data/lib/hecks/runtime/registry/ask_verification.rb +15 -4
- data/lib/hecks/runtime/registry/{chapter_verification.rb → bluebook_verification.rb} +23 -23
- data/lib/hecks/runtime/registry/context_maps.rb +30 -0
- data/lib/hecks/runtime/registry/declarations.rb +8 -6
- data/lib/hecks/runtime/registry/durability_warnings.rb +18 -2
- data/lib/hecks/runtime/registry/hecksagon_rules.rb +27 -24
- data/lib/hecks/runtime/registry/plugin_query_verification.rb +45 -0
- data/lib/hecks/runtime/registry/providers.rb +34 -99
- data/lib/hecks/runtime/registry/query_verification.rb +30 -21
- data/lib/hecks/runtime/registry/runtime_state.rb +4 -3
- data/lib/hecks/runtime/registry/saga_persistence.rb +7 -16
- data/lib/hecks/runtime/registry/verification.rb +9 -5
- data/lib/hecks/runtime/registry/world_defaults.rb +8 -5
- data/lib/hecks/runtime/registry.rb +52 -27
- data/lib/hecks/runtime/row_sweep.rb +104 -0
- data/lib/hecks/runtime/saga_interpreter/compensation.rb +5 -4
- data/lib/hecks/runtime/saga_interpreter/delivery.rb +13 -10
- data/lib/hecks/runtime/saga_interpreter/redrive.rb +141 -0
- data/lib/hecks/runtime/saga_interpreter/transitions.rb +35 -6
- data/lib/hecks/runtime/saga_interpreter.rb +36 -4
- data/lib/hecks/runtime/saga_pending_dispatch.rb +4 -2
- data/lib/hecks/runtime/scalar_field.rb +73 -0
- data/lib/hecks/runtime/tenant_scope.rb +2 -1
- data/lib/hecks/runtime/validation.rb +24 -0
- data/lib/hecks/runtime/value/admission.rb +11 -11
- data/lib/hecks/runtime/value/coercion.rb +7 -7
- data/lib/hecks/runtime/value/field_checks.rb +13 -13
- data/lib/hecks/runtime/value/invariant_violation.rb +4 -1
- data/lib/hecks/runtime/value/references.rb +7 -4
- data/lib/hecks/runtime/value/validation.rb +7 -4
- data/lib/hecks/runtime/wasm_plugin.rb +98 -0
- data/lib/hecks/runtime.rb +14 -14
- data/lib/hecks/rust_build/command_schemas.rb +27 -0
- data/lib/hecks/rust_build/conformance/surface.rb +50 -7
- data/lib/hecks/rust_build/conformance.rb +25 -3
- data/lib/hecks/rust_build/conformance_fuzz.rb +2 -2
- data/lib/hecks/rust_build/domain_name.rb +4 -1
- data/lib/hecks/rust_build/host.rb +15 -9
- data/lib/hecks/rust_build/native_build.rb +2 -1
- data/lib/hecks/rust_build/prebuilt_binary.rb +25 -5
- data/lib/hecks/rust_build/project_rust/codegen_run.rb +11 -10
- data/lib/hecks/rust_build/project_rust/domain_loader.rb +15 -2
- data/lib/hecks/rust_build/project_rust/root_mod/notes.rb +1 -1
- data/lib/hecks/rust_build/project_rust/target_ir.rb +12 -3
- data/lib/hecks/rust_build/project_rust.rb +39 -19
- data/lib/hecks/rust_build/target_dir.rb +40 -0
- data/lib/hecks/rust_build/wasm.rb +46 -17
- data/lib/hecks/rust_build/wasm_browser.rb +8 -2
- data/lib/hecks/rust_build.rb +29 -2
- data/lib/hecks/sentence_form.rb +212 -0
- data/lib/hecks/served_options.rb +2 -1
- data/lib/hecks/shape_ledger.rb +149 -0
- data/lib/hecks/site/bluebook/site.bluebook +39 -62
- data/lib/hecks/site/bluebook/site.ports.hecksagon +1 -1
- data/lib/hecks/sme/bluebook/sme.bluebook +7 -1
- data/lib/hecks/storehouse/audit.rb +4 -1
- data/lib/hecks/storehouse/bluebook_words.rb +35 -0
- data/lib/hecks/storehouse/dispatching.rb +6 -3
- data/lib/hecks/storehouse/introspection.rb +4 -4
- data/lib/hecks/storehouse/querying.rb +2 -2
- data/lib/hecks/storehouse.rb +14 -4
- data/lib/hecks/switches.rb +21 -0
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +5 -9
- data/lib/hecks/text.rb +57 -0
- data/lib/hecks/tickets/bluebook/tickets.bluebook +30 -51
- data/lib/hecks/tickets/bluebook/tickets.hecksagon +1 -1
- data/lib/hecks/tickets/bluebook/tickets.ports.hecksagon +1 -1
- data/lib/hecks/tools/argument_gate_matrix/dispatching.rb +1 -1
- data/lib/hecks/tools/argument_gate_matrix/rows.rb +7 -7
- data/lib/hecks/tools/argument_gate_matrix/violations.rb +1 -1
- data/lib/hecks/tools/argument_gate_matrix.rb +3 -3
- data/lib/hecks/tools/ci_gate_decision.rb +5 -2
- data/lib/hecks/tools/ci_gates.rb +1 -1
- data/lib/hecks/tools/comment_style/caps_words.rb +11 -8
- data/lib/hecks/tools/comment_style/dictionary.rb +7 -2
- data/lib/hecks/tools/comment_style/fixing.rb +10 -5
- data/lib/hecks/tools/comment_style/long_blocks.rb +1 -1
- data/lib/hecks/tools/comment_style/masking.rb +5 -5
- data/lib/hecks/tools/comment_style/run.rb +4 -1
- data/lib/hecks/tools/comment_style/source_file.rb +10 -3
- data/lib/hecks/tools/comment_style/structure_checks.rb +13 -4
- data/lib/hecks/tools/comment_style/text_checks.rb +1 -1
- data/lib/hecks/tools/comment_style.rb +25 -2
- data/lib/hecks/tools/deploy_recipe/generation.rb +1 -1
- data/lib/hecks/tools/deploy_recipe/targets.rb +1 -1
- data/lib/hecks/tools/deploy_recipe/worlds.rb +15 -9
- data/lib/hecks/tools/deploy_recipe.rb +17 -10
- data/lib/hecks/tools/deploy_recipe_lint/fixtures.rb +1 -1
- data/lib/hecks/tools/deploy_recipe_lint/makefile.rb +7 -1
- data/lib/hecks/tools/drop_implicit_append_fields.rb +4 -1
- data/lib/hecks/tools/era_projection_backfill.rb +11 -6
- data/lib/hecks/tools/era_reattest.rb +11 -6
- data/lib/hecks/tools/fuzz_sweep/findings.rb +18 -2
- data/lib/hecks/tools/fuzz_sweep/options.rb +5 -1
- data/lib/hecks/tools/fuzz_sweep.rb +17 -5
- data/lib/hecks/tools/gate.rb +27 -5
- data/lib/hecks/tools/hoist_local_givens/rewriting.rb +3 -2
- data/lib/hecks/tools/hoist_local_givens.rb +2 -4
- data/lib/hecks/tools/lanes/prebuilt_workflow.rb +1 -1
- data/lib/hecks/tools/lanes/rows.rb +9 -3
- data/lib/hecks/tools/lanes/ruleset.rb +1 -1
- data/lib/hecks/tools/lanes/watch_workflow.rb +1 -1
- data/lib/hecks/tools/lanes/workflow.rb +1 -1
- data/lib/hecks/tools/lanes.rb +1 -1
- data/lib/hecks/tools/oidc_manifests.rb +16 -19
- data/lib/hecks/tools/regeneration_run.rb +22 -17
- data/lib/hecks/tools/release_only.rb +5 -2
- data/lib/hecks/tools/rust_comment_style/caps_words.rb +5 -5
- data/lib/hecks/tools/rust_comment_style/fixing.rb +1 -1
- data/lib/hecks/tools/rust_comment_style/masking.rb +7 -4
- data/lib/hecks/tools/rust_comment_style/source_file.rb +7 -2
- data/lib/hecks/tools/rust_comment_style/text_checks.rb +1 -1
- data/lib/hecks/tools/rust_comment_style/tokenizer.rb +4 -1
- data/lib/hecks/tools/site_routes/editor_files.rb +16 -13
- data/lib/hecks/tools/site_routes/root_files.rb +14 -13
- data/lib/hecks/tools/site_routes/templates.rb +5 -4
- data/lib/hecks/tools/site_routes.rb +18 -15
- data/lib/hecks/tools/tail_merge.rb +3 -3
- data/lib/hecks/tools/tenant_provisioning.rb +6 -6
- data/lib/hecks/tools/tools_doc.rb +16 -16
- data/lib/hecks/tools/translation_audit/domain_loading.rb +3 -3
- data/lib/hecks/tools/translation_audit.rb +15 -4
- data/lib/hecks/tools/translation_scaffold/reporting.rb +4 -1
- data/lib/hecks/tools/translation_scaffold.rb +4 -14
- data/lib/hecks/vendoring/git_source.rb +7 -4
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +110 -3
- data/lib/hecks.rb +37 -18
- data/lib/rubocop/cop/hecks/inline_regexp.rb +29 -0
- data/lib/rubocop/cop/hecks/no_transport_in_runtime.rb +78 -0
- data/lib/rubocop/cop/hecks/rails_model_rules.rb +46 -0
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +1 -1
- data/lib/rubocop/cop/hecks/train_wreck.rb +69 -0
- data/lib/rubocop/cop/hecks/unnamed_pipeline.rb +51 -0
- data/prebuilt/31cf81e12f16236ad26249be590cfea3799c9db67432e6a4ef54b801c355b6ae.marshal +0 -0
- data/prebuilt/68ecb2bd3abb1a221007db4f2b0375834c13aaccac8f2b99778e8517f11aa113.marshal +0 -0
- data/prebuilt/verdicts-0328c2403383837d780ad5ed81d1da351c56436b894cf9015b5d56b8f1f1bf10.json +1 -0
- data/qa/settings.yml +9 -0
- data/rust/Cargo.lock +8 -1
- data/rust/Cargo.toml +6 -7
- data/rust/build/src/binding_pass.rs +13 -17
- data/rust/build/src/cargo_sync.rs +1 -1
- data/rust/build/src/lineage_pass.rs +4 -71
- data/rust/build/src/pipeline.rs +99 -70
- data/rust/build/src/resolve.rs +85 -29
- data/rust/build/src/seams_pass.rs +117 -99
- data/rust/build/src/world_pass.rs +21 -12
- data/rust/codegen/src/domain_generator.rs +24 -2
- data/rust/codegen/src/expr_emitter.rs +6 -0
- data/rust/codegen/src/json_codec.rs +48 -10
- data/rust/codegen/src/main.rs +35 -34
- data/rust/codegen/src/manifest.rs +1 -1
- data/rust/codegen/src/naming.rs +5 -5
- data/rust/codegen/src/one_of_references.rs +124 -0
- data/rust/codegen/src/presenters.rs +3 -3
- data/rust/codegen/src/queries.rs +56 -7
- data/rust/codegen/src/reactions.rs +19 -14
- data/rust/codegen/src/read_models.rs +19 -14
- data/rust/codegen/src/registry.rs +89 -15
- data/rust/codegen/src/sidecars.rs +1 -1
- data/rust/expr/Cargo.lock +54 -0
- data/rust/expr/Cargo.toml +52 -0
- data/rust/{src/kernel → expr/src}/attribute_shapes/composite.rs +45 -14
- data/rust/{src/kernel → expr/src}/attribute_shapes/list.rs +1 -1
- data/rust/{src/kernel → expr/src}/attribute_shapes/mod.rs +2 -2
- data/rust/{src/kernel → expr/src}/attribute_shapes/optional.rs +1 -1
- data/rust/{src/kernel → expr/src}/attribute_shapes/scalar.rs +3 -3
- data/rust/{src/kernel → expr/src}/expr.rs +30 -8
- data/rust/{src/kernel → expr/src}/expression_operators/arithmetic.rs +41 -18
- data/rust/{src/kernel → expr/src}/expression_operators/comparison.rs +54 -8
- data/rust/{src/kernel → expr/src}/expression_operators/enumeration.rs +73 -6
- data/rust/{src/kernel → expr/src}/expression_operators/logical.rs +2 -2
- data/rust/expr/src/expression_operators/membership.rs +33 -0
- data/rust/{src/kernel → expr/src}/expression_operators/mod.rs +2 -2
- data/rust/expr/src/expression_operators/pattern_match.rs +179 -0
- data/rust/{src/kernel → expr/src}/expression_operators/positional.rs +8 -6
- data/rust/{src/kernel → expr/src}/expression_operators/presence.rs +7 -6
- data/rust/{src/kernel → expr/src}/expression_operators/sign_test.rs +14 -4
- data/rust/{src/kernel → expr/src}/expression_operators/sized.rs +16 -9
- data/rust/{src/kernel → expr/src}/expression_operators/text.rs +30 -17
- data/rust/{src/kernel → expr/src}/expression_operators/to_string.rs +8 -6
- data/rust/expr/src/lib.rs +17 -0
- data/rust/expr/src/refusal.rs +62 -0
- data/rust/expr/src/rendering.rs +135 -0
- data/rust/{src/kernel → expr/src}/ruby_float.rs +7 -6
- data/rust/host/Cargo.lock +137 -123
- data/rust/host/Cargo.toml +7 -3
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/core/Cargo.toml +64 -0
- data/rust/host/core/clippy.toml +4 -0
- data/rust/host/core/src/arg_limits.rs +48 -0
- data/rust/host/core/src/clock.rs +146 -0
- data/rust/host/core/src/dispatch.rs +920 -0
- data/rust/host/core/src/expr_json.rs +1137 -0
- data/rust/host/{src → core/src}/host.rs +13 -14
- data/rust/host/{src → core/src}/ir.rs +227 -18
- data/rust/host/core/src/journal.rs +126 -0
- data/rust/host/{src → core/src}/lambda_client.rs +27 -96
- data/rust/host/core/src/lib.rs +37 -0
- data/rust/host/{src → core/src}/needs.rs +66 -20
- data/rust/host/core/src/plugins.rs +269 -0
- data/rust/host/core/src/presentation.rs +471 -0
- data/rust/host/{src → core/src}/presentation_write.rs +75 -43
- data/rust/host/{src → core/src}/query_step.rs +54 -16
- data/rust/host/{src → core/src}/reference_validate.rs +6 -6
- data/rust/host/{src → core/src}/storage_shape.rs +2 -2
- data/rust/host/core/src/store/memory.rs +635 -0
- data/rust/host/core/src/store.rs +418 -0
- data/rust/host/{src → core/src}/ui_schema.rs +8 -8
- data/rust/host/core/src/validation.rs +129 -0
- data/rust/host/core/src/wasm_runner.rs +410 -0
- data/rust/host/fuzz/.gitignore +4 -0
- data/rust/host/fuzz/Cargo.lock +4479 -0
- data/rust/host/fuzz/Cargo.toml +42 -0
- data/rust/host/fuzz/README.md +55 -0
- data/rust/host/fuzz/fuzz_targets/postgres_equivalence.rs +24 -0
- data/rust/host/fuzz/src/backend.rs +273 -0
- data/rust/host/fuzz/src/equivalence.rs +195 -0
- data/rust/host/fuzz/src/footprint.rs +182 -0
- data/rust/host/fuzz/src/lib.rs +17 -0
- data/rust/host/fuzz/src/ops.rs +345 -0
- data/rust/host/fuzz/tests/smoke.rs +50 -0
- data/rust/host/src/api.rs +316 -105
- data/rust/host/src/auth/google_signin.rs +183 -0
- data/rust/host/src/auth.rs +527 -370
- data/rust/host/src/aws_lambda_invoker.rs +61 -0
- data/rust/host/src/bin/expr_harness.rs +9 -4
- data/rust/host/src/bin/ledger.rs +84 -0
- data/rust/host/src/bin/lineage_harness.rs +17 -21
- data/rust/host/src/bin/mint_harness.rs +11 -36
- data/rust/host/src/bin/validate_harness.rs +1 -6
- data/rust/host/src/boot.rs +465 -174
- data/rust/host/src/boundary_fuzz/ir.rs +10 -5
- data/rust/host/src/boundary_fuzz/server.rs +6 -5
- data/rust/host/src/checkout.rs +4 -1
- data/rust/host/src/commerce_ir.rs +126 -105
- data/rust/host/src/{dispatch.rs → dispatch_tests.rs} +367 -847
- data/rust/host/src/extension.rs +3 -4
- data/rust/host/src/hecksagon_boundary_tests.rs +149 -0
- data/rust/host/src/ledger.rs +683 -0
- data/rust/host/src/lib.rs +21 -28
- data/rust/host/src/payments/tests.rs +136 -38
- data/rust/host/src/payments.rs +36 -32
- data/rust/host/src/platform_signin/extension.rs +1 -1
- data/rust/host/src/platform_signin/mapping_tests.rs +12 -9
- data/rust/host/src/platform_signin/session.rs +7 -14
- data/rust/host/src/platform_signin/verify.rs +0 -2
- data/rust/host/src/presentation_tests.rs +382 -0
- data/rust/host/src/rate_limit.rs +18 -2
- data/rust/host/src/server.rs +222 -43
- data/rust/host/src/{journal.rs → store/journal_sql.rs} +21 -127
- data/rust/host/src/{mint.rs → store/mint.rs} +12 -12
- data/rust/host/src/store/postgres.rs +493 -0
- data/rust/host/src/store.rs +97 -0
- data/rust/host/src/store_conformance.rs +1265 -0
- data/rust/host/src/web/commerce.rs +10 -18
- data/rust/host/src/web/newsletter/cooldown.rs +107 -22
- data/rust/host/src/web/newsletter/naming.rs +104 -0
- data/rust/host/src/web/newsletter/tests.rs +53 -0
- data/rust/host/src/web/newsletter.rs +73 -50
- data/rust/host/src/web/newsletter_send.rs +12 -13
- data/rust/host/src/web/registration_receipt.rs +2 -2
- data/rust/host/src/web/registrations.rs +754 -375
- data/rust/host/src/web/resend_webhook.rs +3 -3
- data/rust/host/src/web/signin_tests.rs +131 -0
- data/rust/host/src/web.rs +1479 -149
- data/rust/lsp/README.md +27 -27
- data/rust/lsp/src/diagnostics.rs +75 -15
- data/rust/lsp/src/main.rs +101 -5
- data/rust/parser/fuzz/Cargo.toml +2 -2
- data/rust/parser/fuzz/README.md +3 -3
- data/rust/parser/fuzz/fuzz_targets/{chapter.rs → bluebook.rs} +4 -4
- data/rust/parser/fuzz/fuzz_targets/resolve.rs +2 -2
- data/rust/parser/src/build/named_pattern.rs +22 -0
- data/rust/parser/src/build/references.rs +52 -0
- data/rust/parser/src/diag.rs +22 -1
- data/rust/parser/src/emit.rs +68 -4
- data/rust/parser/src/expr/ast_json.rs +10 -0
- data/rust/parser/src/expr/resolver.rs +49 -9
- data/rust/parser/src/ir.rs +34 -7
- data/rust/parser/src/keywords.rs +454 -457
- data/rust/parser/src/lex.rs +115 -87
- data/rust/parser/src/main.rs +180 -32
- data/rust/parser/src/parse/adapter.rs +28 -0
- data/rust/parser/src/parse/aggregate.rs +55 -35
- data/rust/parser/src/parse/command.rs +100 -15
- data/rust/parser/src/parse/{chapter.rs → declaration.rs} +85 -54
- data/rust/parser/src/parse/domain_port.rs +60 -9
- data/rust/parser/src/parse/entity.rs +35 -32
- data/rust/parser/src/parse/file.rs +1 -1
- data/rust/parser/src/parse/hecksagon.rs +15 -5
- data/rust/parser/src/parse/mod.rs +84 -2
- data/rust/parser/src/parse/process_manager.rs +1 -1
- data/rust/parser/src/parse/query.rs +13 -3
- data/rust/parser/src/parse/read_model.rs +2 -1
- data/rust/parser/src/phrases.rs +1258 -0
- data/rust/parser/src/render_ruby.rs +540 -0
- data/rust/parser/src/sentence/changes.rs +437 -0
- data/rust/parser/src/sentence/command_step.rs +206 -0
- data/rust/parser/src/sentence/complete.rs +276 -0
- data/rust/parser/src/sentence/entity.rs +41 -0
- data/rust/parser/src/sentence/examples.rs +1012 -0
- data/rust/parser/src/sentence/format.rs +882 -0
- data/rust/parser/src/sentence/lower.rs +1366 -0
- data/rust/parser/src/sentence/mod.rs +444 -0
- data/rust/parser/src/sentence/names.rs +69 -0
- data/rust/parser/src/sentence/options.rs +154 -0
- data/rust/parser/src/sentence/pattern.rs +433 -0
- data/rust/parser/src/sentence/process.rs +196 -0
- data/rust/parser/src/sentence/queries.rs +431 -0
- data/rust/parser/src/sentence/report.rs +283 -0
- data/rust/parser/src/sentence/token.rs +106 -0
- data/rust/parser/src/sentence/values.rs +498 -0
- data/rust/src/exemplar/queries.rs +1 -0
- data/rust/src/exemplar/reactions.rs +1 -1
- data/rust/src/exemplar/registry.rs +30 -0
- data/rust/src/kernel/cli.rs +219 -19
- data/rust/src/kernel/collecting.rs +174 -0
- data/rust/src/kernel/dispatch.rs +55 -27
- data/rust/src/kernel/mod.rs +7 -67
- data/rust/src/kernel/named_query.rs +225 -11
- data/rust/src/kernel/orchestrate.rs +213 -25
- data/rust/src/kernel/orchestrate_guard_tests.rs +3 -0
- data/rust/src/kernel/query_cursor.rs +251 -0
- data/rust/src/kernel/query_ordering.rs +6 -6
- data/rust/src/kernel/read_model.rs +162 -17
- data/rust/src/kernel/repository.rs +107 -17
- data/rust/src/kernel/vocab/mod.rs +4 -0
- data/rust/src/kernel/vocab/reducer.rs +109 -0
- data/rust/src/kernel/vocab/refusal_class.rs +124 -0
- data/rust/src/kernel/vocab/refusal_template.rs +2 -2
- data/rust/src/lib.rs +1 -1
- data/rust/web/Cargo.lock +47 -0
- metadata +328 -93
- data/lib/hecks/chapters.rb +0 -147
- data/lib/hecks/gate/stages.yml +0 -234
- data/lib/hecks/hecks/governance.world +0 -7
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +0 -137
- data/lib/hecks/ports/persistence/postgres_dump/connection.rb +0 -59
- data/lib/hecks/ports/persistence/postgres_dump.rb +0 -100
- data/lib/hecks/projections/deploy/box/templates/tunnel-egress.yaml.tmpl +0 -11
- data/lib/hecks/projector/exporter/capability_values.rb +0 -55
- data/lib/hecks/runtime/remote_dispatcher.rb +0 -176
- data/prebuilt/ad68f9e06c8068c2a4176b57abf3083d591d5d8980c2b3e897b97331f965b643.marshal +0 -0
- data/prebuilt/ee22ec8642c4006717ba2b80f46e5efb81de459eef9dfec043e838c0ad4fd720.marshal +0 -0
- data/prebuilt/verdicts-b69f5dcbba79c20daa0493476fa7c317004a30aa2bc3d3dc6a722ea70ae2b43b.json +0 -1
- data/rust/host/src/clock.rs +0 -8
- data/rust/host/src/expr_json.rs +0 -1983
- data/rust/host/src/presentation.rs +0 -842
- data/rust/host/src/wasm_runner.rs +0 -206
- data/rust/src/kernel/expression_operators/membership.rs +0 -20
- data/rust/src/kernel/expression_operators/pattern_match.rs +0 -108
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/events.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/head_writes.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/journal.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/base_sql.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/field_cache.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/head_compiler.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/merge_writes.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/provisioning.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/resumable_backfill.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/transform_installer.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/lineage/transform_sql.rb +0 -0
- /data/lib/hecks/{ports/persistence/plugins/era → adapters/driven}/postgres_era/sagas.rb +0 -0
- /data/rust/host/{src → core/src}/field_hints.rs +0 -0
- /data/rust/host/{src → core/src}/log.rs +0 -0
- /data/rust/host/{src → core/src}/reference_transform.rs +0 -0
data/rust/host/src/auth.rs
CHANGED
|
@@ -5,17 +5,24 @@ use crate::b64;
|
|
|
5
5
|
use crate::crypto::{constant_time_eq, hex_encode};
|
|
6
6
|
use crate::dispatch;
|
|
7
7
|
use crate::host::Host;
|
|
8
|
-
use crate::journal;
|
|
9
|
-
use crate::journal::LineageConfig;
|
|
10
8
|
use crate::lambda_client::LambdaInvoker;
|
|
9
|
+
use crate::needs::{Clock, ProcessClock};
|
|
10
|
+
use crate::store::journal_sql as journal;
|
|
11
|
+
use crate::store::journal_sql::LineageConfig;
|
|
12
|
+
use crate::store::{Store, StoreError};
|
|
11
13
|
use serde_json::{json, Value};
|
|
12
14
|
use std::path::Path;
|
|
13
15
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
|
|
17
|
+
mod google_signin;
|
|
18
|
+
|
|
19
|
+
pub use google_signin::GoogleSignin;
|
|
20
|
+
pub use google_signin::{
|
|
21
|
+
clear_header as google_signin_clear_header, read as read_google_signin,
|
|
22
|
+
set_header as google_signin_set_header,
|
|
23
|
+
};
|
|
16
24
|
|
|
17
25
|
const ISSUER: &str = "https://accounts.google.com";
|
|
18
|
-
const STATE_TTL_SECS: u64 = 600;
|
|
19
26
|
|
|
20
27
|
/// A verified Google ID token's claims.
|
|
21
28
|
pub struct Claims {
|
|
@@ -33,32 +40,33 @@ pub struct Session {
|
|
|
33
40
|
pub role: Option<String>,
|
|
34
41
|
}
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
/// The caller a signed-in session dispatches as: the role it carries and the identity
|
|
44
|
+
/// Governance holds assignments for. No session is an unidentified caller.
|
|
45
|
+
pub fn caller(session: Option<&Session>) -> crate::dispatch::Caller<'_> {
|
|
46
|
+
session.map_or_else(crate::dispatch::Caller::default, |session| {
|
|
47
|
+
crate::dispatch::Caller {
|
|
48
|
+
role: session.role.as_deref(),
|
|
49
|
+
actor_id: Some(session.identity_id.as_str()).filter(|id| !id.is_empty()),
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Google's consent page for one sign-in: `signin`'s `state` comes back on the callback and its
|
|
55
|
+
/// `nonce` inside the ID token, and both must match the cookie `/auth/google` set beside them.
|
|
56
|
+
pub fn authorization_url(redirect_uri: &str, signin: &GoogleSignin) -> Result<String, String> {
|
|
37
57
|
let client_id = crate::secrets::var("GOOGLE_CLIENT_ID")
|
|
38
58
|
.map_err(|_| "GOOGLE_CLIENT_ID not set".to_string())?;
|
|
39
|
-
let payload = now_secs().to_string();
|
|
40
|
-
let state = format!("{payload}.{}", sign(secret, &payload));
|
|
41
59
|
Ok(format!(
|
|
42
|
-
"https://accounts.google.com/o/oauth2/v2/auth?client_id={}&redirect_uri={}&response_type=code&scope={}&state={}",
|
|
60
|
+
"https://accounts.google.com/o/oauth2/v2/auth?client_id={}&redirect_uri={}&response_type=code&scope={}&state={}&nonce={}",
|
|
43
61
|
urlencode(&client_id),
|
|
44
62
|
urlencode(redirect_uri),
|
|
45
63
|
urlencode("openid email profile"),
|
|
46
|
-
urlencode(&state),
|
|
64
|
+
urlencode(&signin.state),
|
|
65
|
+
urlencode(&signin.nonce),
|
|
47
66
|
))
|
|
48
67
|
}
|
|
49
68
|
|
|
50
|
-
|
|
51
|
-
// is the whole check: a forged, mismatched, or expired state fails on it.
|
|
52
|
-
pub fn verify_state(state: &str, secret: &str) -> Result<(), String> {
|
|
53
|
-
let payload = verify_sig(secret, state).ok_or_else(|| "state mismatch".to_string())?;
|
|
54
|
-
let minted: u64 = payload.parse().map_err(|_| "state mismatch".to_string())?;
|
|
55
|
-
if now_secs().saturating_sub(minted) > STATE_TTL_SECS {
|
|
56
|
-
return Err("state expired".to_string());
|
|
57
|
-
}
|
|
58
|
-
Ok(())
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
pub async fn verify(code: &str, redirect_uri: &str) -> Result<Claims, String> {
|
|
69
|
+
pub async fn verify(code: &str, redirect_uri: &str, nonce: &str) -> Result<Claims, String> {
|
|
62
70
|
let client_id = crate::secrets::var("GOOGLE_CLIENT_ID")
|
|
63
71
|
.map_err(|_| "GOOGLE_CLIENT_ID not set".to_string())?;
|
|
64
72
|
let client_secret = crate::secrets::var("GOOGLE_CLIENT_SECRET")
|
|
@@ -91,13 +99,13 @@ pub async fn verify(code: &str, redirect_uri: &str) -> Result<Claims, String> {
|
|
|
91
99
|
.and_then(|v| v.as_str())
|
|
92
100
|
.ok_or_else(|| "no id_token in the response".to_string())?;
|
|
93
101
|
|
|
94
|
-
verify_id_token(id_token, &client_id).await
|
|
102
|
+
verify_id_token(id_token, &client_id, nonce).await
|
|
95
103
|
}
|
|
96
104
|
|
|
97
105
|
// Fetched fresh, never cached: this is a real sign-in, not a hot path,
|
|
98
106
|
// and missing Google's own key rotation would be worse than one extra
|
|
99
107
|
// HTTPS round trip per login.
|
|
100
|
-
async fn verify_id_token(id_token: &str, client_id: &str) -> Result<Claims, String> {
|
|
108
|
+
async fn verify_id_token(id_token: &str, client_id: &str, nonce: &str) -> Result<Claims, String> {
|
|
101
109
|
let header =
|
|
102
110
|
jsonwebtoken::decode_header(id_token).map_err(|e| format!("bad id_token header: {e}"))?;
|
|
103
111
|
let kid = header
|
|
@@ -140,6 +148,7 @@ async fn verify_id_token(id_token: &str, client_id: &str) -> Result<Claims, Stri
|
|
|
140
148
|
let token = jsonwebtoken::decode::<Value>(id_token, &decoding_key, &validation)
|
|
141
149
|
.map_err(|err| format!("id_token signature/claims invalid: {err}"))?;
|
|
142
150
|
let claims = token.claims;
|
|
151
|
+
check_nonce(&claims, nonce)?;
|
|
143
152
|
|
|
144
153
|
Ok(Claims {
|
|
145
154
|
issuer: claims
|
|
@@ -161,6 +170,17 @@ async fn verify_id_token(id_token: &str, client_id: &str) -> Result<Claims, Stri
|
|
|
161
170
|
})
|
|
162
171
|
}
|
|
163
172
|
|
|
173
|
+
// The ID token must carry the nonce this browser's sign-in cookie holds, so a token minted for
|
|
174
|
+
// someone else's sign-in cannot finish this one.
|
|
175
|
+
fn check_nonce(claims: &Value, expected: &str) -> Result<(), String> {
|
|
176
|
+
let carried = claims.get("nonce").and_then(Value::as_str).unwrap_or("");
|
|
177
|
+
if constant_time_eq(carried.as_bytes(), expected.as_bytes()) {
|
|
178
|
+
Ok(())
|
|
179
|
+
} else {
|
|
180
|
+
Err("id_token nonce does not match this sign-in".to_string())
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
164
184
|
/// The cookie name the account token travels in when a deploy names none.
|
|
165
185
|
/// A deploy naming its own uses `HECKS_SESSION_COOKIE` instead.
|
|
166
186
|
pub const DEFAULT_ACCOUNT_COOKIE: &str = "hecks_session";
|
|
@@ -234,6 +254,39 @@ fn purpose_key(secret: &str, purpose: &str) -> String {
|
|
|
234
254
|
format!("{purpose}:{secret}")
|
|
235
255
|
}
|
|
236
256
|
|
|
257
|
+
const HANDOFF_PURPOSE: &str = "google-handoff";
|
|
258
|
+
const HANDOFF_TTL_SECS: u64 = 60;
|
|
259
|
+
|
|
260
|
+
/// A one-minute, single-use stand-in for `email`'s account token. It rides the redirect onto a
|
|
261
|
+
/// site on another origin, where a URL is logged and kept in history, and the site trades it for
|
|
262
|
+
/// the account token server-to-server (`redeem_handoff_token`).
|
|
263
|
+
pub fn handoff_token(secret: &str, email: &str) -> String {
|
|
264
|
+
let jti = uuid::Uuid::new_v4().simple().to_string();
|
|
265
|
+
purpose_token(
|
|
266
|
+
secret,
|
|
267
|
+
HANDOFF_PURPOSE,
|
|
268
|
+
json!({ "email": email, "jti": jti }),
|
|
269
|
+
HANDOFF_TTL_SECS,
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/// The account token a handoff token stands for, the first time it is redeemed; `None` when it is
|
|
274
|
+
/// forged, expired, minted for another purpose, or already spent in `spent`.
|
|
275
|
+
pub fn redeem_handoff_token(
|
|
276
|
+
secret: &str,
|
|
277
|
+
token: &str,
|
|
278
|
+
spent: &crate::platform_signin::jti::JtiStore,
|
|
279
|
+
) -> Option<String> {
|
|
280
|
+
let claims = verify_purpose_token(secret, HANDOFF_PURPOSE, token)?;
|
|
281
|
+
let email = claims.get("email")?.as_str()?;
|
|
282
|
+
let jti = claims.get("jti")?.as_str()?;
|
|
283
|
+
let expires_at = i64::try_from(claims.get("exp")?.as_u64()?).ok()?;
|
|
284
|
+
let now = i64::try_from(now_secs()).ok()?;
|
|
285
|
+
spent
|
|
286
|
+
.claim(jti, expires_at + 1, now)
|
|
287
|
+
.then(|| account_token(secret, email, SESSION_TTL_SECS))
|
|
288
|
+
}
|
|
289
|
+
|
|
237
290
|
/// How long a session cookie, and the account token minted beside it, stays valid.
|
|
238
291
|
pub const SESSION_TTL_SECS: u64 = 60 * 60 * 24 * 14;
|
|
239
292
|
|
|
@@ -302,7 +355,7 @@ fn identity_id_from_state(state: &Value) -> Option<String> {
|
|
|
302
355
|
}
|
|
303
356
|
|
|
304
357
|
/// Resolves an external identity the way the Google callback does: the PostgresEra head first,
|
|
305
|
-
/// then the flat journal's `instances`. A domain whose Identity
|
|
358
|
+
/// then the flat journal's `instances`. A domain whose Identity bluebook keeps no head of its own
|
|
306
359
|
/// (its heads cover its own aggregates and the membership aggregate) holds the link only there.
|
|
307
360
|
///
|
|
308
361
|
/// # Errors
|
|
@@ -311,18 +364,18 @@ fn identity_id_from_state(state: &Value) -> Option<String> {
|
|
|
311
364
|
/// head that cannot be read is logged and skipped, as the Google callback does, so the flat journal
|
|
312
365
|
/// still gets its turn.
|
|
313
366
|
pub async fn resolve_identity_anywhere(
|
|
314
|
-
|
|
367
|
+
store: &Store,
|
|
315
368
|
wasm_path: &Path,
|
|
316
369
|
domain_ir: &Value,
|
|
317
370
|
issuer: &str,
|
|
318
371
|
subject: &str,
|
|
319
372
|
) -> anyhow::Result<Option<String>> {
|
|
320
|
-
match resolve_identity_from_head(
|
|
373
|
+
match resolve_identity_from_head(store, domain_ir, issuer, subject).await {
|
|
321
374
|
Ok(Some(found)) => return Ok(Some(found)),
|
|
322
375
|
Ok(None) => {}
|
|
323
376
|
Err(error) => eprintln!("resolve_identity: identity head: {error:#}"),
|
|
324
377
|
}
|
|
325
|
-
let read = match dispatch::read(
|
|
378
|
+
let read = match dispatch::read(store, wasm_path).await {
|
|
326
379
|
Ok(read) => read,
|
|
327
380
|
Err(error) if flat_journal_is_absent(&error) => return Ok(None),
|
|
328
381
|
Err(error) => return Err(error),
|
|
@@ -338,14 +391,14 @@ pub async fn resolve_identity_anywhere(
|
|
|
338
391
|
fn flat_journal_is_absent(error: &anyhow::Error) -> bool {
|
|
339
392
|
error
|
|
340
393
|
.chain()
|
|
341
|
-
.filter_map(|cause| cause.downcast_ref::<
|
|
342
|
-
.any(
|
|
394
|
+
.filter_map(|cause| cause.downcast_ref::<StoreError>())
|
|
395
|
+
.any(StoreError::is_not_provisioned)
|
|
343
396
|
}
|
|
344
397
|
|
|
345
398
|
/// Identity lives in the PostgresEra head, not `dispatch::read`'s
|
|
346
399
|
/// `instances` — tries both the domain-qualified and the bare relation name.
|
|
347
400
|
pub async fn resolve_identity_from_head(
|
|
348
|
-
|
|
401
|
+
store: &Store,
|
|
349
402
|
domain_ir: &Value,
|
|
350
403
|
issuer: &str,
|
|
351
404
|
subject: &str,
|
|
@@ -354,36 +407,21 @@ pub async fn resolve_identity_from_head(
|
|
|
354
407
|
return Ok(None);
|
|
355
408
|
};
|
|
356
409
|
let domain = domain_ir.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
|
357
|
-
let
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
let sql = format!(
|
|
366
|
-
"SELECT state FROM {} WHERE state->'issuer'->>'value' = $1 AND state->'subject'->>'value' = $2",
|
|
367
|
-
journal::quote_ident(&name)
|
|
368
|
-
);
|
|
369
|
-
match guard.query_opt(&sql, &[&issuer, &subject]).await {
|
|
370
|
-
Ok(Some(row)) => {
|
|
371
|
-
let state: Value = row.get(0);
|
|
372
|
-
return Ok(identity_id_from_state(&state));
|
|
373
|
-
}
|
|
374
|
-
Ok(None) => return Ok(None),
|
|
375
|
-
Err(e) if e.code() == Some(&tokio_postgres::error::SqlState::UNDEFINED_TABLE) => {}
|
|
376
|
-
Err(e) => return Err(e.into()),
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
Ok(None)
|
|
410
|
+
let found = store
|
|
411
|
+
.head_find(
|
|
412
|
+
&[domain, "Identity"],
|
|
413
|
+
"external_identifier",
|
|
414
|
+
&[("issuer", issuer), ("subject", subject)],
|
|
415
|
+
)
|
|
416
|
+
.await?;
|
|
417
|
+
Ok(found.and_then(|state| identity_id_from_state(&state)))
|
|
380
418
|
}
|
|
381
419
|
|
|
382
420
|
// Member lives in the PostgresEra head, not `dispatch::read`'s journal
|
|
383
421
|
// `instances` — reading `instances` for an already-linked member misses
|
|
384
422
|
// it entirely, so this queries the head view directly instead.
|
|
385
423
|
async fn member_row_by_email(
|
|
386
|
-
|
|
424
|
+
store: &Store,
|
|
387
425
|
domain_ir: &Value,
|
|
388
426
|
email: &str,
|
|
389
427
|
) -> anyhow::Result<Option<Value>> {
|
|
@@ -391,27 +429,22 @@ async fn member_row_by_email(
|
|
|
391
429
|
// docs/decisions/0059: `head_view` needs the domain name too, the
|
|
392
430
|
// same one `ir.rs`/`web.rs` already extract this way.
|
|
393
431
|
let domain = domain_ir.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
|
394
|
-
|
|
395
|
-
journal::read_lineage_head_by_id(&*guard, domain, &storage_name, email).await
|
|
432
|
+
Ok(store.head_by_id(domain, &storage_name, email).await?)
|
|
396
433
|
}
|
|
397
434
|
|
|
398
|
-
async fn member_rows(
|
|
399
|
-
client: &Mutex<Client>,
|
|
400
|
-
domain_ir: &Value,
|
|
401
|
-
) -> anyhow::Result<Vec<(String, Value)>> {
|
|
435
|
+
async fn member_rows(store: &Store, domain_ir: &Value) -> anyhow::Result<Vec<(String, Value)>> {
|
|
402
436
|
let (_, storage_name) = membership_aggregate(domain_ir)?;
|
|
403
437
|
let domain = domain_ir.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
|
404
|
-
|
|
405
|
-
journal::read_lineage_head_all(&*guard, domain, &storage_name).await
|
|
438
|
+
Ok(store.head_all(domain, &storage_name).await?)
|
|
406
439
|
}
|
|
407
440
|
|
|
408
441
|
// The membership aggregate comes from `ir.json`'s declared `membership`
|
|
409
442
|
// key, never an env var or a lookup against lineage_capable_aggregates —
|
|
410
|
-
// the
|
|
443
|
+
// the bluebook declaration is the fact, same as Governance's authorization.
|
|
411
444
|
fn membership_aggregate(domain_ir: &Value) -> anyhow::Result<(String, String)> {
|
|
412
445
|
let provider = crate::ir::membership_provider(domain_ir).ok_or_else(|| {
|
|
413
446
|
anyhow::anyhow!(
|
|
414
|
-
"this domain attaches no
|
|
447
|
+
"this domain attaches no bluebook that provides \"membership\" — cannot resolve who may sign in"
|
|
415
448
|
)
|
|
416
449
|
})?;
|
|
417
450
|
Ok(membership_names(&provider.aggregate))
|
|
@@ -423,7 +456,7 @@ fn membership_names(aggregate: &str) -> (String, String) {
|
|
|
423
456
|
.next()
|
|
424
457
|
.unwrap_or(aggregate)
|
|
425
458
|
.to_string();
|
|
426
|
-
let storage_name = crate::
|
|
459
|
+
let storage_name = crate::store::journal_sql::snake(&bare);
|
|
427
460
|
(bare, storage_name)
|
|
428
461
|
}
|
|
429
462
|
|
|
@@ -431,60 +464,40 @@ fn membership_names(aggregate: &str) -> (String, String) {
|
|
|
431
464
|
// + snapshot-upsert transaction every Member write uses. The advisory
|
|
432
465
|
// lock guards a concurrent domain rename, not ordinal uniqueness.
|
|
433
466
|
async fn append_member_state(
|
|
434
|
-
|
|
467
|
+
store: &Store,
|
|
435
468
|
config: &LineageConfig,
|
|
436
469
|
domain_ir: &Value,
|
|
437
470
|
id: &str,
|
|
438
471
|
state: &Value,
|
|
439
472
|
) -> anyhow::Result<()> {
|
|
440
473
|
let (aggregate_name, _) = membership_aggregate(domain_ir)?;
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
config,
|
|
453
|
-
&journal::Mutation {
|
|
454
|
-
aggregate: &aggregate_name,
|
|
455
|
-
id,
|
|
456
|
-
operation: "save",
|
|
457
|
-
state,
|
|
458
|
-
},
|
|
459
|
-
)
|
|
460
|
-
.await?;
|
|
461
|
-
|
|
462
|
-
txn.commit().await?;
|
|
474
|
+
store
|
|
475
|
+
.put_head(
|
|
476
|
+
config,
|
|
477
|
+
&journal::Mutation {
|
|
478
|
+
aggregate: &aggregate_name,
|
|
479
|
+
id,
|
|
480
|
+
operation: "save",
|
|
481
|
+
state,
|
|
482
|
+
},
|
|
483
|
+
)
|
|
484
|
+
.await?;
|
|
463
485
|
Ok(())
|
|
464
486
|
}
|
|
465
487
|
|
|
466
488
|
pub async fn session_for_member_by_identity(
|
|
467
|
-
|
|
489
|
+
store: &Store,
|
|
468
490
|
domain_ir: &Value,
|
|
469
491
|
identity_id: &str,
|
|
470
492
|
) -> anyhow::Result<Option<Session>> {
|
|
471
493
|
let (_, storage_name) = membership_aggregate(domain_ir)?;
|
|
472
494
|
// docs/decisions/0059 — same domain-qualification as above.
|
|
473
495
|
let domain = domain_ir.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
|
474
|
-
let
|
|
475
|
-
|
|
476
|
-
.query_opt(
|
|
477
|
-
&format!(
|
|
478
|
-
"SELECT state FROM {} WHERE state->'identity_id'->>'value' = $1",
|
|
479
|
-
journal::quote_ident(&journal::head_view(domain, &storage_name))
|
|
480
|
-
),
|
|
481
|
-
&[&identity_id],
|
|
482
|
-
)
|
|
496
|
+
let row = store
|
|
497
|
+
.head_find(&[domain], &storage_name, &[("identity_id", identity_id)])
|
|
483
498
|
.await?;
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
let state: Value = r.get(0);
|
|
487
|
-
if is_disabled(&state) {
|
|
499
|
+
Ok(row.and_then(|state| {
|
|
500
|
+
if is_lapsed(&state) {
|
|
488
501
|
return None;
|
|
489
502
|
}
|
|
490
503
|
Some(Session {
|
|
@@ -504,7 +517,7 @@ pub async fn session_for_member_by_identity(
|
|
|
504
517
|
/// never creates one; Admit is a separate, real cap-table event.
|
|
505
518
|
#[allow(clippy::too_many_arguments)]
|
|
506
519
|
pub async fn provision(
|
|
507
|
-
|
|
520
|
+
store: &Store,
|
|
508
521
|
wasm_path: &Path,
|
|
509
522
|
config: &LineageConfig,
|
|
510
523
|
domain_ir: &Value,
|
|
@@ -513,10 +526,10 @@ pub async fn provision(
|
|
|
513
526
|
subject: &str,
|
|
514
527
|
invoker: &dyn LambdaInvoker,
|
|
515
528
|
) -> anyhow::Result<Option<Session>> {
|
|
516
|
-
let Some(member) = member_row_by_email(
|
|
529
|
+
let Some(member) = member_row_by_email(store, domain_ir, email).await? else {
|
|
517
530
|
return Ok(None);
|
|
518
531
|
};
|
|
519
|
-
if
|
|
532
|
+
if is_lapsed(&member) {
|
|
520
533
|
return Ok(None);
|
|
521
534
|
}
|
|
522
535
|
let role = member
|
|
@@ -532,10 +545,10 @@ pub async fn provision(
|
|
|
532
545
|
};
|
|
533
546
|
|
|
534
547
|
// Uses this domain's declared identity provider (`ir::identity_provider`),
|
|
535
|
-
// never a hard-coded
|
|
548
|
+
// never a hard-coded bluebook. Link's reference field is `identity`, not
|
|
536
549
|
// `identity_id` -- changing this silently breaks linking.
|
|
537
550
|
let Some(identity) = crate::ir::identity_provider(domain_ir) else {
|
|
538
|
-
anyhow::bail!("this domain attaches no
|
|
551
|
+
anyhow::bail!("this domain attaches no bluebook that provides \"identity\" — cannot register or link an identity");
|
|
539
552
|
};
|
|
540
553
|
|
|
541
554
|
// Reuse an Identity already registered+linked for this (issuer, subject)
|
|
@@ -551,7 +564,7 @@ pub async fn provision(
|
|
|
551
564
|
// first makes a retry converge onto the earlier attempt's identity
|
|
552
565
|
// instead of accumulating another orphan.
|
|
553
566
|
let identity_id = if let Some(existing) =
|
|
554
|
-
resolve_identity_from_head(
|
|
567
|
+
resolve_identity_from_head(store, domain_ir, issuer, subject).await?
|
|
555
568
|
{
|
|
556
569
|
existing
|
|
557
570
|
} else {
|
|
@@ -561,7 +574,7 @@ pub async fn provision(
|
|
|
561
574
|
// provisioning, not a caller-submitted command, so there is no
|
|
562
575
|
// caller role to assert.
|
|
563
576
|
let register = dispatch::handle(
|
|
564
|
-
Host::new(
|
|
577
|
+
Host::new(store, wasm_path, config, invoker),
|
|
565
578
|
&identity.register,
|
|
566
579
|
json!({"identity_id": {"value": identity_id}}),
|
|
567
580
|
None,
|
|
@@ -572,7 +585,7 @@ pub async fn provision(
|
|
|
572
585
|
}
|
|
573
586
|
|
|
574
587
|
let link_external = dispatch::handle(
|
|
575
|
-
Host::new(
|
|
588
|
+
Host::new(store, wasm_path, config, invoker),
|
|
576
589
|
&identity.link,
|
|
577
590
|
json!({
|
|
578
591
|
"identity": identity_id, "key": {"value": format!("{issuer}:{subject}")},
|
|
@@ -589,15 +602,15 @@ pub async fn provision(
|
|
|
589
602
|
};
|
|
590
603
|
|
|
591
604
|
// The grant verb this domain's declared authorization provider names
|
|
592
|
-
// (`ir::authorization_provider`), never a hard-coded
|
|
605
|
+
// (`ir::authorization_provider`), never a hard-coded bluebook.
|
|
593
606
|
let Some(provider) = crate::ir::authorization_provider(domain_ir) else {
|
|
594
607
|
anyhow::bail!(
|
|
595
|
-
"this domain attaches no
|
|
608
|
+
"this domain attaches no bluebook that provides \"authorization\" — cannot grant a role"
|
|
596
609
|
);
|
|
597
610
|
};
|
|
598
|
-
let assign_role = dispatch::handle(Host::new(
|
|
611
|
+
let assign_role = dispatch::handle(Host::new(store, wasm_path, config, invoker), &provider.grant, json!({
|
|
599
612
|
"actor_id": {"value": identity_id}, "role_name": {"value": role}, "scope": {"value": config.domain.clone()},
|
|
600
|
-
"starts_at": {"value":
|
|
613
|
+
"starts_at": {"value": crate::clock::iso8601_now()},
|
|
601
614
|
}), None).await?;
|
|
602
615
|
if !assign_role.accepted {
|
|
603
616
|
anyhow::bail!("{} refused: {}", provider.grant, assign_role.result);
|
|
@@ -608,7 +621,7 @@ pub async fn provision(
|
|
|
608
621
|
// refuse. Writes through the same journal+snapshot append instead.
|
|
609
622
|
let mut new_state = member.clone();
|
|
610
623
|
new_state["identity_id"] = json!({"value": identity_id});
|
|
611
|
-
append_member_state(
|
|
624
|
+
append_member_state(store, config, domain_ir, email, &new_state).await?;
|
|
612
625
|
|
|
613
626
|
let name = member
|
|
614
627
|
.get("name")
|
|
@@ -625,37 +638,87 @@ pub async fn provision(
|
|
|
625
638
|
}
|
|
626
639
|
|
|
627
640
|
pub async fn grant_access(
|
|
628
|
-
|
|
641
|
+
store: &Store,
|
|
629
642
|
wasm_path: &Path,
|
|
630
643
|
config: &LineageConfig,
|
|
631
644
|
domain_ir: &Value,
|
|
632
645
|
email: &str,
|
|
633
646
|
role: &str,
|
|
634
647
|
) -> anyhow::Result<bool> {
|
|
635
|
-
let
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
648
|
+
let outcome =
|
|
649
|
+
grant_access_until(store, wasm_path, config, domain_ir, email, role, None).await?;
|
|
650
|
+
Ok(outcome == GrantOutcome::Done)
|
|
651
|
+
}
|
|
639
652
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
653
|
+
/// What `grant_access_until` decided.
|
|
654
|
+
#[derive(Debug, PartialEq, Eq)]
|
|
655
|
+
pub enum GrantOutcome {
|
|
656
|
+
/// The role (and window, if any) is now on the person's row.
|
|
657
|
+
Done,
|
|
658
|
+
/// No person with that email exists.
|
|
659
|
+
UnknownPerson,
|
|
660
|
+
/// The change would leave no active admin without an end date.
|
|
661
|
+
LastOpenAdmin,
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/// Grants `role` like `grant_access`, for a window ending at `expires_at`
|
|
665
|
+
/// (an ISO 8601 UTC instant) when one is given. Granting again renews: the
|
|
666
|
+
/// new window replaces the old, and `None` clears it, so a plain grant makes
|
|
667
|
+
/// the access open-ended. Refused when it would take the site's last
|
|
668
|
+
/// open-ended admin off that footing, locked with other membership writes.
|
|
669
|
+
pub async fn grant_access_until(
|
|
670
|
+
store: &Store,
|
|
671
|
+
wasm_path: &Path,
|
|
672
|
+
config: &LineageConfig,
|
|
673
|
+
domain_ir: &Value,
|
|
674
|
+
email: &str,
|
|
675
|
+
role: &str,
|
|
676
|
+
expires_at: Option<&str>,
|
|
677
|
+
) -> anyhow::Result<GrantOutcome> {
|
|
678
|
+
let _ = wasm_path;
|
|
679
|
+
let (aggregate_name, storage_name) = membership_aggregate(domain_ir)?;
|
|
680
|
+
let domain = domain_ir.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
|
681
|
+
let target = email.trim().to_lowercase();
|
|
682
|
+
|
|
683
|
+
let outcome = store
|
|
684
|
+
.amend_head(config, domain, &aggregate_name, &storage_name, |rows| {
|
|
685
|
+
let Some((id, member)) = rows.iter().find(|(id, _)| id.to_lowercase() == target) else {
|
|
686
|
+
return (GrantOutcome::UnknownPerson, None);
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
// Same append protocol `provision` uses. GrantAccess's own bluebook
|
|
690
|
+
// command sets only `:role`; the window is a host-side field beside it,
|
|
691
|
+
// like `disabled`, so it needs no bluebook attribute.
|
|
692
|
+
let mut new_state = member.clone();
|
|
693
|
+
new_state["role"] = json!({"value": role});
|
|
694
|
+
match expires_at {
|
|
695
|
+
Some(instant) => new_state[EXPIRY_FIELD] = json!(instant),
|
|
696
|
+
None => {
|
|
697
|
+
if let Some(fields) = new_state.as_object_mut() {
|
|
698
|
+
fields.remove(EXPIRY_FIELD);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if strands_open_admin(rows, id, &new_state) {
|
|
703
|
+
return (GrantOutcome::LastOpenAdmin, None);
|
|
704
|
+
}
|
|
705
|
+
(GrantOutcome::Done, Some((id.clone(), new_state)))
|
|
706
|
+
})
|
|
707
|
+
.await?;
|
|
708
|
+
Ok(outcome)
|
|
646
709
|
}
|
|
647
710
|
|
|
648
711
|
/// Admits a new person (name + email), the same state `Admit` sets.
|
|
649
712
|
/// Returns `false` without writing when that email already exists.
|
|
650
713
|
pub async fn admit_person(
|
|
651
|
-
|
|
714
|
+
store: &Store,
|
|
652
715
|
config: &LineageConfig,
|
|
653
716
|
domain_ir: &Value,
|
|
654
717
|
email: &str,
|
|
655
718
|
name: &str,
|
|
656
719
|
) -> anyhow::Result<bool> {
|
|
657
720
|
let email = email.to_lowercase();
|
|
658
|
-
let exists = member_rows(
|
|
721
|
+
let exists = member_rows(store, domain_ir)
|
|
659
722
|
.await?
|
|
660
723
|
.into_iter()
|
|
661
724
|
.any(|(id, _)| id.to_lowercase() == email);
|
|
@@ -663,7 +726,7 @@ pub async fn admit_person(
|
|
|
663
726
|
return Ok(false);
|
|
664
727
|
}
|
|
665
728
|
let state = json!({"name": {"value": name}, "email": {"value": email}});
|
|
666
|
-
append_member_state(
|
|
729
|
+
append_member_state(store, config, domain_ir, &email, &state).await?;
|
|
667
730
|
Ok(true)
|
|
668
731
|
}
|
|
669
732
|
|
|
@@ -676,6 +739,77 @@ fn is_disabled(state: &Value) -> bool {
|
|
|
676
739
|
.unwrap_or(false)
|
|
677
740
|
}
|
|
678
741
|
|
|
742
|
+
// The row field holding the end of a time-boxed access window, an ISO 8601
|
|
743
|
+
// UTC instant. It sits beside `disabled` in the row state, so a grant that
|
|
744
|
+
// carries no window leaves the person with open-ended access as before.
|
|
745
|
+
const EXPIRY_FIELD: &str = "access_expires_at";
|
|
746
|
+
|
|
747
|
+
// The window's end as written on the row: a bare string, or a `{"value": ..}`
|
|
748
|
+
// wrapper as other timestamp attributes carry.
|
|
749
|
+
enum Window {
|
|
750
|
+
// No window is set (absent or null).
|
|
751
|
+
Open,
|
|
752
|
+
// A window ending at this many epoch seconds.
|
|
753
|
+
Ends(i64),
|
|
754
|
+
// A window is set but is not a readable instant; it counts as ended.
|
|
755
|
+
Unreadable,
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
fn window_of(state: &Value) -> Window {
|
|
759
|
+
let Some(raw) = state.get(EXPIRY_FIELD).filter(|v| !v.is_null()) else {
|
|
760
|
+
return Window::Open;
|
|
761
|
+
};
|
|
762
|
+
let text = raw.get("value").unwrap_or(raw).as_str();
|
|
763
|
+
text.and_then(crate::clock::parse_iso_utc)
|
|
764
|
+
.map_or(Window::Unreadable, Window::Ends)
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// The window's end for display: the stored text, exactly as written.
|
|
768
|
+
fn expiry_text(state: &Value) -> Option<String> {
|
|
769
|
+
let raw = state.get(EXPIRY_FIELD).filter(|v| !v.is_null())?;
|
|
770
|
+
let text = raw.get("value").unwrap_or(raw);
|
|
771
|
+
Some(
|
|
772
|
+
text.as_str()
|
|
773
|
+
.map_or_else(|| text.to_string(), str::to_string),
|
|
774
|
+
)
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// Whether the row's access window has ended at `now` (epoch seconds). An
|
|
778
|
+
// unreadable value counts as ended: access fails closed.
|
|
779
|
+
fn is_expired_at(state: &Value, now: i64) -> bool {
|
|
780
|
+
match window_of(state) {
|
|
781
|
+
Window::Open => false,
|
|
782
|
+
Window::Unreadable => true,
|
|
783
|
+
Window::Ends(ends) => ends <= now,
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Whether the row grants no access right now: disabled, or its window ended.
|
|
788
|
+
// Every check that lets a person in or counts an admin asks this, never
|
|
789
|
+
// `is_disabled` alone; disabling and enabling keep the raw flag.
|
|
790
|
+
fn is_lapsed(state: &Value) -> bool {
|
|
791
|
+
is_lapsed_at(state, ProcessClock.now_secs())
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
fn is_lapsed_at(state: &Value, now: i64) -> bool {
|
|
795
|
+
is_disabled(state) || is_expired_at(state, now)
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/// The ISO 8601 UTC instant `days` days after `now` (epoch seconds), the
|
|
799
|
+
/// value `grant_access_until` stores as the end of a window.
|
|
800
|
+
pub fn expiry_after_days_from(days: u32, now: i64) -> String {
|
|
801
|
+
crate::clock::iso_utc((now + i64::from(days) * 86_400).max(0) as u64)
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
/// `expiry_after_days_from` counted from the process clock, which a spec
|
|
805
|
+
/// can fix (`needs::set_fixed_clock`).
|
|
806
|
+
pub fn expiry_after_days(days: u32) -> String {
|
|
807
|
+
expiry_after_days_from(days, ProcessClock.now_secs())
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/// The longest window a grant may carry, in days.
|
|
811
|
+
pub const MAX_EXPIRY_DAYS: u32 = 365;
|
|
812
|
+
|
|
679
813
|
// A deleted person's row is never erased (this is an append-only journal,
|
|
680
814
|
// same as every other aggregate here) — "deleted" only means all_people
|
|
681
815
|
// stops listing it. There is deliberately no undelete route: re-admitting
|
|
@@ -708,9 +842,10 @@ pub fn is_admin_role(role: &str) -> bool {
|
|
|
708
842
|
ADMIN_ROLES.contains(&role)
|
|
709
843
|
}
|
|
710
844
|
|
|
711
|
-
// Whether the row holds an admin role ("Admin" or "Owner") and is not disabled
|
|
845
|
+
// Whether the row holds an admin role ("Admin" or "Owner") and is not disabled
|
|
846
|
+
// or past its access window.
|
|
712
847
|
fn is_active_admin(state: &Value) -> bool {
|
|
713
|
-
role_of(state).is_some_and(is_admin_role) && !
|
|
848
|
+
role_of(state).is_some_and(is_admin_role) && !is_lapsed(state)
|
|
714
849
|
}
|
|
715
850
|
|
|
716
851
|
// Whether `state` is the only active admin (Admin or Owner) among `rows`.
|
|
@@ -718,60 +853,82 @@ fn is_last_active_admin(rows: &[(String, Value)], state: &Value) -> bool {
|
|
|
718
853
|
is_active_admin(state) && rows.iter().filter(|(_, s)| is_active_admin(s)).count() <= 1
|
|
719
854
|
}
|
|
720
855
|
|
|
856
|
+
// Whether the row is an active admin with no end date: the kind of access a
|
|
857
|
+
// site keeps at least one of, because a window's lapse cannot be prevented.
|
|
858
|
+
fn is_open_admin(state: &Value) -> bool {
|
|
859
|
+
is_active_admin(state) && matches!(window_of(state), Window::Open)
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// Whether replacing the row `id` with `after` would leave the site with no
|
|
863
|
+
// open-ended admin where it had one, or would make the site's first admin one
|
|
864
|
+
// with an end date. A site that has admins but none open-ended (it was never
|
|
865
|
+
// given one) is not made worse by a change, so this stays false there.
|
|
866
|
+
fn strands_open_admin(rows: &[(String, Value)], id: &str, after: &Value) -> bool {
|
|
867
|
+
let had = rows.iter().any(|(_, s)| is_open_admin(s));
|
|
868
|
+
let keeps = is_open_admin(after)
|
|
869
|
+
|| rows
|
|
870
|
+
.iter()
|
|
871
|
+
.any(|(other, s)| other.to_lowercase() != id.to_lowercase() && is_open_admin(s));
|
|
872
|
+
let first_admin = !rows
|
|
873
|
+
.iter()
|
|
874
|
+
.any(|(_, s)| role_of(s).is_some_and(is_admin_role));
|
|
875
|
+
let windowed_first = first_admin && role_of(after).is_some_and(is_admin_role) && !keeps;
|
|
876
|
+
(had && !keeps) || windowed_first
|
|
877
|
+
}
|
|
878
|
+
|
|
721
879
|
/// Whether the person with `email` (compared case-insensitively) is an
|
|
722
|
-
/// active admin: granted the "Admin" or "Owner" role and not
|
|
880
|
+
/// active admin: granted the "Admin" or "Owner" role, not disabled, and not
|
|
881
|
+
/// past its access window.
|
|
723
882
|
pub async fn caller_is_admin(
|
|
724
|
-
|
|
883
|
+
store: &Store,
|
|
725
884
|
domain_ir: &Value,
|
|
726
885
|
email: &str,
|
|
727
886
|
) -> anyhow::Result<bool> {
|
|
728
887
|
let email = email.to_lowercase();
|
|
729
|
-
Ok(member_rows(
|
|
888
|
+
Ok(member_rows(store, domain_ir)
|
|
730
889
|
.await?
|
|
731
890
|
.iter()
|
|
732
891
|
.any(|(id, state)| id.to_lowercase() == email && is_active_admin(state)))
|
|
733
892
|
}
|
|
734
893
|
|
|
735
894
|
/// Whether `email` may hold a session right now: admitted, granted a role,
|
|
736
|
-
/// and
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
domain_ir: &Value,
|
|
740
|
-
email: &str,
|
|
741
|
-
) -> anyhow::Result<bool> {
|
|
895
|
+
/// and neither disabled nor past its access window — a signature alone can't
|
|
896
|
+
/// tell, since a cookie outlives a later disable or the end of the window.
|
|
897
|
+
pub async fn has_access(store: &Store, domain_ir: &Value, email: &str) -> anyhow::Result<bool> {
|
|
742
898
|
let email = email.to_lowercase();
|
|
743
|
-
Ok(member_rows(
|
|
899
|
+
Ok(member_rows(store, domain_ir)
|
|
744
900
|
.await?
|
|
745
901
|
.iter()
|
|
746
902
|
.any(|(id, state)| {
|
|
747
|
-
id.to_lowercase() == email && role_of(state).is_some() && !
|
|
903
|
+
id.to_lowercase() == email && role_of(state).is_some() && !is_lapsed(state)
|
|
748
904
|
}))
|
|
749
905
|
}
|
|
750
906
|
|
|
751
907
|
/// The role `email` holds right now, or `None` if unknown, roleless, or
|
|
752
|
-
/// disabled — reads the live membership head, never the
|
|
908
|
+
/// disabled or past its window — reads the live membership head, never the
|
|
909
|
+
/// session cookie.
|
|
753
910
|
pub async fn active_role(
|
|
754
|
-
|
|
911
|
+
store: &Store,
|
|
755
912
|
domain_ir: &Value,
|
|
756
913
|
email: &str,
|
|
757
914
|
) -> anyhow::Result<Option<String>> {
|
|
758
915
|
let email = email.to_lowercase();
|
|
759
|
-
Ok(member_rows(
|
|
916
|
+
Ok(member_rows(store, domain_ir)
|
|
760
917
|
.await?
|
|
761
918
|
.iter()
|
|
762
|
-
.find(|(id, state)| id.to_lowercase() == email && !
|
|
919
|
+
.find(|(id, state)| id.to_lowercase() == email && !is_lapsed(state))
|
|
763
920
|
.and_then(|(_, state)| role_of(state).map(String::from)))
|
|
764
921
|
}
|
|
765
922
|
|
|
766
923
|
/// The Governance identity id linked to the member with `email`, read from
|
|
767
924
|
/// the live membership head. `None` when the member has not signed in yet.
|
|
768
925
|
pub async fn member_identity_id(
|
|
769
|
-
|
|
926
|
+
store: &Store,
|
|
770
927
|
domain_ir: &Value,
|
|
771
928
|
email: &str,
|
|
772
929
|
) -> anyhow::Result<Option<String>> {
|
|
773
930
|
let email = email.to_lowercase();
|
|
774
|
-
Ok(member_rows(
|
|
931
|
+
Ok(member_rows(store, domain_ir)
|
|
775
932
|
.await?
|
|
776
933
|
.iter()
|
|
777
934
|
.find(|(id, state)| id.to_lowercase() == email && !is_deleted(state))
|
|
@@ -788,10 +945,13 @@ fn linked_identity_id(state: &Value) -> Option<String> {
|
|
|
788
945
|
}
|
|
789
946
|
|
|
790
947
|
/// Every admitted person as a JSON row: name, email, role, `linked`
|
|
791
|
-
/// (signed in), `granted` (has access), `disabled
|
|
948
|
+
/// (signed in), `granted` (has access right now), `disabled` (the raw flag),
|
|
949
|
+
/// `access_expires_at` (the end of a time-boxed window, `null` when open-ended),
|
|
950
|
+
/// `expired` (the window has ended or is unreadable), and `identity_id`
|
|
792
951
|
/// (the Governance identity id, `null` until the person has signed in).
|
|
793
|
-
pub async fn all_people(
|
|
794
|
-
|
|
952
|
+
pub async fn all_people(store: &Store, domain_ir: &Value) -> anyhow::Result<Vec<Value>> {
|
|
953
|
+
let now = ProcessClock.now_secs();
|
|
954
|
+
Ok(member_rows(store, domain_ir)
|
|
795
955
|
.await?
|
|
796
956
|
.into_iter()
|
|
797
957
|
.filter(|(_, state)| !is_deleted(state))
|
|
@@ -802,13 +962,16 @@ pub async fn all_people(client: &Mutex<Client>, domain_ir: &Value) -> anyhow::Re
|
|
|
802
962
|
.and_then(|v| v.get("value"))
|
|
803
963
|
.is_some();
|
|
804
964
|
let disabled = is_disabled(&state);
|
|
965
|
+
let expired = is_expired_at(&state, now);
|
|
805
966
|
json!({
|
|
806
967
|
"name": state.get("name").and_then(|v| v.get("value")),
|
|
807
968
|
"email": state.get("email").and_then(|v| v.get("value")),
|
|
808
969
|
"role": role,
|
|
809
970
|
"linked": linked,
|
|
810
|
-
"granted": role.is_some() && !disabled,
|
|
971
|
+
"granted": role.is_some() && !disabled && !expired,
|
|
811
972
|
"disabled": disabled,
|
|
973
|
+
"access_expires_at": expiry_text(&state),
|
|
974
|
+
"expired": expired,
|
|
812
975
|
"identity_id": linked_identity_id(&state),
|
|
813
976
|
})
|
|
814
977
|
})
|
|
@@ -828,12 +991,14 @@ pub enum DisableOutcome {
|
|
|
828
991
|
UnknownPerson,
|
|
829
992
|
/// Disabling would leave no active Admin.
|
|
830
993
|
LastAdmin,
|
|
994
|
+
/// Disabling would leave no active Admin without an end date.
|
|
995
|
+
LastOpenAdmin,
|
|
831
996
|
}
|
|
832
997
|
|
|
833
998
|
/// Disables or enables `target` for `caller`, keeping role and identity
|
|
834
999
|
/// link — locked with other membership writes so concurrent disables can't both win.
|
|
835
1000
|
pub async fn set_person_disabled(
|
|
836
|
-
|
|
1001
|
+
store: &Store,
|
|
837
1002
|
config: &LineageConfig,
|
|
838
1003
|
domain_ir: &Value,
|
|
839
1004
|
caller: &str,
|
|
@@ -844,53 +1009,40 @@ pub async fn set_person_disabled(
|
|
|
844
1009
|
let domain = domain_ir.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
|
845
1010
|
let (caller, target) = (caller.trim().to_lowercase(), target.trim().to_lowercase());
|
|
846
1011
|
|
|
847
|
-
let
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
};
|
|
868
|
-
if is_disabled(state) == disable {
|
|
869
|
-
return Ok(DisableOutcome::Done);
|
|
870
|
-
}
|
|
871
|
-
if disable && is_last_active_admin(&rows, state) {
|
|
872
|
-
return Ok(DisableOutcome::LastAdmin);
|
|
873
|
-
}
|
|
1012
|
+
let outcome = store
|
|
1013
|
+
.amend_head(config, domain, &aggregate_name, &storage_name, |rows| {
|
|
1014
|
+
if !rows
|
|
1015
|
+
.iter()
|
|
1016
|
+
.any(|(id, state)| id.to_lowercase() == caller && is_active_admin(state))
|
|
1017
|
+
{
|
|
1018
|
+
return (DisableOutcome::CallerNotAdmin, None);
|
|
1019
|
+
}
|
|
1020
|
+
if disable && caller == target {
|
|
1021
|
+
return (DisableOutcome::SelfDisable, None);
|
|
1022
|
+
}
|
|
1023
|
+
let Some((id, state)) = rows.iter().find(|(id, _)| id.to_lowercase() == target) else {
|
|
1024
|
+
return (DisableOutcome::UnknownPerson, None);
|
|
1025
|
+
};
|
|
1026
|
+
if is_disabled(state) == disable {
|
|
1027
|
+
return (DisableOutcome::Done, None);
|
|
1028
|
+
}
|
|
1029
|
+
if disable && is_last_active_admin(rows, state) {
|
|
1030
|
+
return (DisableOutcome::LastAdmin, None);
|
|
1031
|
+
}
|
|
874
1032
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
state: &new_state,
|
|
889
|
-
},
|
|
890
|
-
)
|
|
891
|
-
.await?;
|
|
892
|
-
txn.commit().await?;
|
|
893
|
-
Ok(DisableOutcome::Done)
|
|
1033
|
+
let mut new_state = state.clone();
|
|
1034
|
+
if disable {
|
|
1035
|
+
new_state["disabled"] = json!(true);
|
|
1036
|
+
} else if let Some(fields) = new_state.as_object_mut() {
|
|
1037
|
+
fields.remove("disabled");
|
|
1038
|
+
}
|
|
1039
|
+
if strands_open_admin(rows, id, &new_state) {
|
|
1040
|
+
return (DisableOutcome::LastOpenAdmin, None);
|
|
1041
|
+
}
|
|
1042
|
+
(DisableOutcome::Done, Some((id.clone(), new_state)))
|
|
1043
|
+
})
|
|
1044
|
+
.await?;
|
|
1045
|
+
Ok(outcome)
|
|
894
1046
|
}
|
|
895
1047
|
|
|
896
1048
|
/// What `set_person_deleted` decided.
|
|
@@ -911,7 +1063,7 @@ pub enum DeleteOutcome {
|
|
|
911
1063
|
/// stays in the journal (nothing here is ever erased) but `all_people` stops
|
|
912
1064
|
/// listing it. Locked with other membership writes the same as disable/role.
|
|
913
1065
|
pub async fn set_person_deleted(
|
|
914
|
-
|
|
1066
|
+
store: &Store,
|
|
915
1067
|
config: &LineageConfig,
|
|
916
1068
|
domain_ir: &Value,
|
|
917
1069
|
caller: &str,
|
|
@@ -921,46 +1073,30 @@ pub async fn set_person_deleted(
|
|
|
921
1073
|
let domain = domain_ir.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
|
922
1074
|
let (caller, target) = (caller.trim().to_lowercase(), target.trim().to_lowercase());
|
|
923
1075
|
|
|
924
|
-
let
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
};
|
|
942
|
-
if is_deleted(state) {
|
|
943
|
-
return Ok(DeleteOutcome::Done);
|
|
944
|
-
}
|
|
945
|
-
if !is_disabled(state) {
|
|
946
|
-
return Ok(DeleteOutcome::NotDisabled);
|
|
947
|
-
}
|
|
1076
|
+
let outcome = store
|
|
1077
|
+
.amend_head(config, domain, &aggregate_name, &storage_name, |rows| {
|
|
1078
|
+
if !rows
|
|
1079
|
+
.iter()
|
|
1080
|
+
.any(|(id, state)| id.to_lowercase() == caller && is_active_admin(state))
|
|
1081
|
+
{
|
|
1082
|
+
return (DeleteOutcome::CallerNotAdmin, None);
|
|
1083
|
+
}
|
|
1084
|
+
let Some((id, state)) = rows.iter().find(|(id, _)| id.to_lowercase() == target) else {
|
|
1085
|
+
return (DeleteOutcome::UnknownPerson, None);
|
|
1086
|
+
};
|
|
1087
|
+
if is_deleted(state) {
|
|
1088
|
+
return (DeleteOutcome::Done, None);
|
|
1089
|
+
}
|
|
1090
|
+
if !is_disabled(state) {
|
|
1091
|
+
return (DeleteOutcome::NotDisabled, None);
|
|
1092
|
+
}
|
|
948
1093
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
aggregate: &aggregate_name,
|
|
956
|
-
id,
|
|
957
|
-
operation: "save",
|
|
958
|
-
state: &new_state,
|
|
959
|
-
},
|
|
960
|
-
)
|
|
961
|
-
.await?;
|
|
962
|
-
txn.commit().await?;
|
|
963
|
-
Ok(DeleteOutcome::Done)
|
|
1094
|
+
let mut new_state = state.clone();
|
|
1095
|
+
new_state["deleted"] = json!(true);
|
|
1096
|
+
(DeleteOutcome::Done, Some((id.clone(), new_state)))
|
|
1097
|
+
})
|
|
1098
|
+
.await?;
|
|
1099
|
+
Ok(outcome)
|
|
964
1100
|
}
|
|
965
1101
|
|
|
966
1102
|
/// What `set_person_role` decided.
|
|
@@ -974,12 +1110,14 @@ pub enum RoleOutcome {
|
|
|
974
1110
|
UnknownPerson,
|
|
975
1111
|
/// The change would leave no active Admin or Owner.
|
|
976
1112
|
LastAdmin,
|
|
1113
|
+
/// The change would leave no active Admin or Owner without an end date.
|
|
1114
|
+
LastOpenAdmin,
|
|
977
1115
|
}
|
|
978
1116
|
|
|
979
1117
|
/// Sets `target`'s role on `caller`'s behalf; any active admin may
|
|
980
1118
|
/// grant any role, Owner included, locked against concurrent demotions.
|
|
981
1119
|
pub async fn set_person_role(
|
|
982
|
-
|
|
1120
|
+
store: &Store,
|
|
983
1121
|
config: &LineageConfig,
|
|
984
1122
|
domain_ir: &Value,
|
|
985
1123
|
caller: &str,
|
|
@@ -990,77 +1128,59 @@ pub async fn set_person_role(
|
|
|
990
1128
|
let domain = domain_ir.get("name").and_then(|v| v.as_str()).unwrap_or("");
|
|
991
1129
|
let (caller, target) = (caller.trim().to_lowercase(), target.trim().to_lowercase());
|
|
992
1130
|
|
|
993
|
-
let
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
};
|
|
1011
|
-
if role_of(state) == Some(role) {
|
|
1012
|
-
return Ok(RoleOutcome::Done);
|
|
1013
|
-
}
|
|
1014
|
-
if !is_admin_role(role) && is_last_active_admin(&rows, state) {
|
|
1015
|
-
return Ok(RoleOutcome::LastAdmin);
|
|
1016
|
-
}
|
|
1131
|
+
let outcome = store
|
|
1132
|
+
.amend_head(config, domain, &aggregate_name, &storage_name, |rows| {
|
|
1133
|
+
if !rows
|
|
1134
|
+
.iter()
|
|
1135
|
+
.any(|(id, state)| id.to_lowercase() == caller && is_active_admin(state))
|
|
1136
|
+
{
|
|
1137
|
+
return (RoleOutcome::CallerNotAdmin, None);
|
|
1138
|
+
}
|
|
1139
|
+
let Some((id, state)) = rows.iter().find(|(id, _)| id.to_lowercase() == target) else {
|
|
1140
|
+
return (RoleOutcome::UnknownPerson, None);
|
|
1141
|
+
};
|
|
1142
|
+
if role_of(state) == Some(role) {
|
|
1143
|
+
return (RoleOutcome::Done, None);
|
|
1144
|
+
}
|
|
1145
|
+
if !is_admin_role(role) && is_last_active_admin(rows, state) {
|
|
1146
|
+
return (RoleOutcome::LastAdmin, None);
|
|
1147
|
+
}
|
|
1017
1148
|
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
state: &new_state,
|
|
1028
|
-
},
|
|
1029
|
-
)
|
|
1030
|
-
.await?;
|
|
1031
|
-
txn.commit().await?;
|
|
1032
|
-
Ok(RoleOutcome::Done)
|
|
1149
|
+
let mut new_state = state.clone();
|
|
1150
|
+
new_state["role"] = json!({"value": role});
|
|
1151
|
+
if strands_open_admin(rows, id, &new_state) {
|
|
1152
|
+
return (RoleOutcome::LastOpenAdmin, None);
|
|
1153
|
+
}
|
|
1154
|
+
(RoleOutcome::Done, Some((id.clone(), new_state)))
|
|
1155
|
+
})
|
|
1156
|
+
.await?;
|
|
1157
|
+
Ok(outcome)
|
|
1033
1158
|
}
|
|
1034
1159
|
|
|
1035
|
-
/// Whether `
|
|
1036
|
-
///
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
)
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
};
|
|
1045
|
-
let prefix = format!("{}#", provider.assignment_aggregate);
|
|
1046
|
-
instances.as_object().is_some_and(|obj| {
|
|
1047
|
-
obj.iter().any(|(key, state)| {
|
|
1048
|
-
key.starts_with(&prefix)
|
|
1049
|
-
&& state
|
|
1050
|
-
.get("actor_id")
|
|
1051
|
-
.and_then(|v| v.get("value"))
|
|
1052
|
-
.and_then(|v| v.as_str())
|
|
1053
|
-
== Some(identity_id)
|
|
1054
|
-
&& state
|
|
1055
|
-
.get("role_name")
|
|
1056
|
-
.and_then(|v| v.get("value"))
|
|
1057
|
-
.and_then(|v| v.as_str())
|
|
1058
|
-
.is_some_and(is_admin_role)
|
|
1059
|
-
&& state.get("ends_at").is_none_or(serde_json::Value::is_null)
|
|
1060
|
-
})
|
|
1160
|
+
/// Whether any of `rows` — the provider's `live_assignments` answer for one actor — grants an
|
|
1161
|
+
/// Admin or Owner role. The bluebook's query decides which assignments are live, so this reads
|
|
1162
|
+
/// no `ends_at`.
|
|
1163
|
+
pub fn holds_admin(rows: &[Value]) -> bool {
|
|
1164
|
+
rows.iter().any(|row| {
|
|
1165
|
+
row.get("role_name")
|
|
1166
|
+
.and_then(|v| v.get("value"))
|
|
1167
|
+
.and_then(|v| v.as_str())
|
|
1168
|
+
.is_some_and(is_admin_role)
|
|
1061
1169
|
})
|
|
1062
1170
|
}
|
|
1063
1171
|
|
|
1172
|
+
/// The rows of a kernel query answer (`{"queries":[{"rows":[...]}]}`).
|
|
1173
|
+
pub fn answer_rows(answer: &Value) -> Vec<Value> {
|
|
1174
|
+
answer
|
|
1175
|
+
.get("queries")
|
|
1176
|
+
.and_then(|q| q.as_array())
|
|
1177
|
+
.and_then(|q| q.first())
|
|
1178
|
+
.and_then(|q| q.get("rows"))
|
|
1179
|
+
.and_then(|r| r.as_array())
|
|
1180
|
+
.cloned()
|
|
1181
|
+
.unwrap_or_default()
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1064
1184
|
fn now_secs() -> u64 {
|
|
1065
1185
|
SystemTime::now()
|
|
1066
1186
|
.duration_since(UNIX_EPOCH)
|
|
@@ -1068,36 +1188,6 @@ fn now_secs() -> u64 {
|
|
|
1068
1188
|
.as_secs()
|
|
1069
1189
|
}
|
|
1070
1190
|
|
|
1071
|
-
// `dispatch.rs`'s `occurred_at` stamping reuses this exact rendering
|
|
1072
|
-
// rather than re-deriving it — see the body comment for the format
|
|
1073
|
-
// guarantee both call sites depend on.
|
|
1074
|
-
pub(crate) fn httpdate_now() -> String {
|
|
1075
|
-
// ISO 8601 UTC, matching Ruby's Time.now.utc.iso8601 -- Governance::
|
|
1076
|
-
// RoleAssignment.starts_at only needs to parse as a real instant,
|
|
1077
|
-
// never re-derived from or compared against wall-clock time here.
|
|
1078
|
-
let secs = now_secs();
|
|
1079
|
-
let days = secs / 86_400;
|
|
1080
|
-
let rem = secs % 86_400;
|
|
1081
|
-
let (h, m, s) = (rem / 3600, (rem % 3600) / 60, rem % 60);
|
|
1082
|
-
let (y, mo, d) = civil_from_days(days as i64);
|
|
1083
|
-
format!("{y:04}-{mo:02}-{d:02}T{h:02}:{m:02}:{s:02}Z")
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
// Howard Hinnant's days-from-civil algorithm, inverted -- avoids
|
|
1087
|
-
// pulling in a full date/time crate for one timestamp format.
|
|
1088
|
-
const fn civil_from_days(z: i64) -> (i64, u32, u32) {
|
|
1089
|
-
let z = z + 719_468;
|
|
1090
|
-
let era = if z >= 0 { z } else { z - 146_096 } / 146_097;
|
|
1091
|
-
let doe = (z - era * 146_097) as u64;
|
|
1092
|
-
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146_096) / 365;
|
|
1093
|
-
let y = yoe as i64 + era * 400;
|
|
1094
|
-
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
|
|
1095
|
-
let mp = (5 * doy + 2) / 153;
|
|
1096
|
-
let d = (doy - (153 * mp + 2) / 5 + 1) as u32;
|
|
1097
|
-
let m = if mp < 10 { mp + 3 } else { mp - 9 } as u32;
|
|
1098
|
-
(if m <= 2 { y + 1 } else { y }, m, d)
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
1191
|
#[allow(clippy::expect_used)] // reason: HMAC accepts a key of any length, so construction cannot fail
|
|
1102
1192
|
fn sign(secret: &str, payload: &str) -> String {
|
|
1103
1193
|
use hmac::{Hmac, Mac};
|
|
@@ -1136,6 +1226,8 @@ pub(crate) fn urlencode(s: &str) -> String {
|
|
|
1136
1226
|
#[cfg(test)]
|
|
1137
1227
|
mod tests {
|
|
1138
1228
|
use super::*;
|
|
1229
|
+
#[allow(unused_imports)]
|
|
1230
|
+
use crate::store::StoreFixtures;
|
|
1139
1231
|
|
|
1140
1232
|
#[test]
|
|
1141
1233
|
fn signed_token_verifies_with_the_right_secret_and_rejects_tampering() {
|
|
@@ -1287,15 +1379,42 @@ mod tests {
|
|
|
1287
1379
|
}
|
|
1288
1380
|
|
|
1289
1381
|
#[test]
|
|
1290
|
-
fn
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
assert!(
|
|
1294
|
-
assert!(
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1382
|
+
fn an_id_token_is_accepted_only_with_the_sign_ins_own_nonce() {
|
|
1383
|
+
assert!(check_nonce(&json!({"nonce": "n-123"}), "n-123").is_ok());
|
|
1384
|
+
assert!(check_nonce(&json!({"nonce": "n-456"}), "n-123").is_err());
|
|
1385
|
+
assert!(check_nonce(&json!({}), "n-123").is_err());
|
|
1386
|
+
assert!(check_nonce(&json!({"nonce": 5}), "n-123").is_err());
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
#[test]
|
|
1390
|
+
fn a_handoff_token_is_redeemed_once_for_the_account_token() {
|
|
1391
|
+
let spent = crate::platform_signin::jti::JtiStore::new();
|
|
1392
|
+
let handoff = handoff_token("s3cret", "zed@example.com");
|
|
1393
|
+
assert_eq!(verify_account_token("s3cret", &handoff), None);
|
|
1394
|
+
let account = redeem_handoff_token("s3cret", &handoff, &spent).unwrap();
|
|
1395
|
+
assert_eq!(
|
|
1396
|
+
verify_account_token("s3cret", &account).as_deref(),
|
|
1397
|
+
Some("zed@example.com")
|
|
1398
|
+
);
|
|
1399
|
+
assert_eq!(redeem_handoff_token("s3cret", &handoff, &spent), None);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
#[test]
|
|
1403
|
+
fn an_account_or_foreign_token_is_not_a_handoff_token() {
|
|
1404
|
+
let spent = crate::platform_signin::jti::JtiStore::new();
|
|
1405
|
+
let account = account_token("s3cret", "zed@example.com", 60);
|
|
1406
|
+
assert_eq!(redeem_handoff_token("s3cret", &account, &spent), None);
|
|
1407
|
+
let handoff = handoff_token("other-secret", "zed@example.com");
|
|
1408
|
+
assert_eq!(redeem_handoff_token("s3cret", &handoff, &spent), None);
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1411
|
+
#[test]
|
|
1412
|
+
fn the_consent_url_carries_the_sign_ins_state_and_nonce() {
|
|
1413
|
+
crate::secrets::provide("GOOGLE_CLIENT_ID", "client-1".to_string());
|
|
1414
|
+
let signin = GoogleSignin::fresh();
|
|
1415
|
+
let url = authorization_url("https://host.example/auth/google/callback", &signin).unwrap();
|
|
1416
|
+
assert!(url.contains(&format!("&state={}", signin.state)), "{url}");
|
|
1417
|
+
assert!(url.contains(&format!("&nonce={}", signin.nonce)), "{url}");
|
|
1299
1418
|
}
|
|
1300
1419
|
|
|
1301
1420
|
#[test]
|
|
@@ -1312,7 +1431,7 @@ mod tests {
|
|
|
1312
1431
|
#[test]
|
|
1313
1432
|
fn resolve_identity_finds_a_link_that_only_the_flat_journal_holds() {
|
|
1314
1433
|
// The shape `ExternalIdentifier.Link` leaves in the flat snapshot of a domain whose Identity
|
|
1315
|
-
//
|
|
1434
|
+
// bluebook has no head of its own; the key carries the issuer URL and the subject.
|
|
1316
1435
|
let instances = json!({
|
|
1317
1436
|
"Identity::ExternalIdentifier#https://accounts.google.com:111": {
|
|
1318
1437
|
"identity": "471ef402-8609-4fa4-9288-6372fc74650d",
|
|
@@ -1356,21 +1475,61 @@ mod tests {
|
|
|
1356
1475
|
}
|
|
1357
1476
|
|
|
1358
1477
|
#[test]
|
|
1359
|
-
fn
|
|
1360
|
-
let
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1478
|
+
fn holds_admin_reads_the_live_assignment_rows() {
|
|
1479
|
+
let live = [json!({"actor_id": {"value": "id-1"}, "role_name": {"value": "Admin"}})];
|
|
1480
|
+
assert!(holds_admin(&live));
|
|
1481
|
+
// No live assignment answered: no role is held, whatever was granted before.
|
|
1482
|
+
assert!(!holds_admin(&[]));
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
#[test]
|
|
1486
|
+
fn answer_rows_reads_the_first_query_of_a_kernel_answer() {
|
|
1487
|
+
let answer = json!({"queries": [{"query": "G.Live", "rows": [{"id": "a"}]}]});
|
|
1488
|
+
assert_eq!(answer_rows(&answer), vec![json!({"id": "a"})]);
|
|
1489
|
+
assert!(answer_rows(&json!({"queries": []})).is_empty());
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
#[test]
|
|
1493
|
+
fn a_window_lapses_exactly_at_its_end_and_a_missing_one_never_does() {
|
|
1494
|
+
let ends = crate::clock::parse_iso_utc("2026-10-22T12:00:00Z").unwrap();
|
|
1495
|
+
let row = json!({"role": {"value": "Member"}, "access_expires_at": "2026-10-22T12:00:00Z"});
|
|
1496
|
+
assert!(!is_lapsed_at(&row, ends - 1), "one second before the end");
|
|
1497
|
+
assert!(is_lapsed_at(&row, ends), "the end instant itself is over");
|
|
1498
|
+
assert!(is_lapsed_at(&row, ends + 1));
|
|
1499
|
+
let open = json!({"role": {"value": "Member"}});
|
|
1500
|
+
assert!(!is_lapsed_at(&open, i64::MAX));
|
|
1501
|
+
let unreadable = json!({"access_expires_at": "next week"});
|
|
1502
|
+
assert!(is_lapsed_at(&unreadable, 0), "unreadable fails closed");
|
|
1503
|
+
let disabled = json!({"disabled": true, "access_expires_at": "2099-01-01T00:00:00Z"});
|
|
1504
|
+
assert!(
|
|
1505
|
+
is_lapsed_at(&disabled, 0),
|
|
1506
|
+
"an open window does not undo a disable"
|
|
1507
|
+
);
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
#[test]
|
|
1511
|
+
fn expiry_after_days_counts_whole_days_from_the_given_instant() {
|
|
1512
|
+
let start = crate::clock::parse_iso_utc("2026-10-22T08:30:15Z").unwrap();
|
|
1513
|
+
assert_eq!(expiry_after_days_from(1, start), "2026-10-23T08:30:15Z");
|
|
1514
|
+
assert_eq!(
|
|
1515
|
+
expiry_after_days_from(MAX_EXPIRY_DAYS, start),
|
|
1516
|
+
"2027-10-22T08:30:15Z"
|
|
1517
|
+
);
|
|
1518
|
+
}
|
|
1365
1519
|
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1520
|
+
#[test]
|
|
1521
|
+
fn is_last_active_admin_ignores_an_admin_whose_window_ended() {
|
|
1522
|
+
let admin = json!({"role": {"value": "Admin"}});
|
|
1523
|
+
let expired =
|
|
1524
|
+
json!({"role": {"value": "Admin"}, "access_expires_at": "2020-01-01T00:00:00Z"});
|
|
1525
|
+
let rows = |states: &[&Value]| -> Vec<(String, Value)> {
|
|
1526
|
+
states
|
|
1527
|
+
.iter()
|
|
1528
|
+
.map(|s| (String::new(), (*s).clone()))
|
|
1529
|
+
.collect()
|
|
1369
1530
|
};
|
|
1370
|
-
assert!(
|
|
1371
|
-
assert!(!
|
|
1372
|
-
// No declared provider: no assignment can exist, so never admin.
|
|
1373
|
-
assert!(!holds_admin(&instances, "id-1", None));
|
|
1531
|
+
assert!(is_last_active_admin(&rows(&[&admin, &expired]), &admin));
|
|
1532
|
+
assert!(!is_last_active_admin(&rows(&[&admin, &expired]), &expired));
|
|
1374
1533
|
}
|
|
1375
1534
|
|
|
1376
1535
|
#[test]
|
|
@@ -1417,15 +1576,12 @@ mod tests {
|
|
|
1417
1576
|
|
|
1418
1577
|
#[test]
|
|
1419
1578
|
fn holds_admin_accepts_an_owner_assignment_and_refuses_other_roles() {
|
|
1420
|
-
let
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
assert!(holds_admin(&
|
|
1426
|
-
assert!(holds_admin(&assignment("Owner"), "id-1", Some(&provider)));
|
|
1427
|
-
assert!(!holds_admin(&assignment("Member"), "id-1", Some(&provider)));
|
|
1428
|
-
assert!(!holds_admin(&assignment("owner"), "id-1", Some(&provider)));
|
|
1579
|
+
let rows =
|
|
1580
|
+
|role: &str| [json!({"actor_id": {"value": "id-1"}, "role_name": {"value": role}})];
|
|
1581
|
+
assert!(holds_admin(&rows("Admin")));
|
|
1582
|
+
assert!(holds_admin(&rows("Owner")));
|
|
1583
|
+
assert!(!holds_admin(&rows("Member")));
|
|
1584
|
+
assert!(!holds_admin(&rows("owner")));
|
|
1429
1585
|
}
|
|
1430
1586
|
|
|
1431
1587
|
#[test]
|
|
@@ -1456,7 +1612,7 @@ mod tests {
|
|
|
1456
1612
|
// A throwaway Postgres database per test, matching the real
|
|
1457
1613
|
// `head_view` naming (`qualified_name(domain, "#{storage_name}_head")`,
|
|
1458
1614
|
// docs/decisions/0059) that the functions under test query directly.
|
|
1459
|
-
async fn scratch_member_db(name: &str) ->
|
|
1615
|
+
async fn scratch_member_db(name: &str) -> Store {
|
|
1460
1616
|
use tokio_postgres::NoTls;
|
|
1461
1617
|
let (admin, conn) = tokio_postgres::connect(&crate::test_pg::conninfo("postgres"), NoTls)
|
|
1462
1618
|
.await
|
|
@@ -1493,7 +1649,7 @@ mod tests {
|
|
|
1493
1649
|
)
|
|
1494
1650
|
.await
|
|
1495
1651
|
.unwrap();
|
|
1496
|
-
|
|
1652
|
+
crate::store::postgres_store(client)
|
|
1497
1653
|
}
|
|
1498
1654
|
|
|
1499
1655
|
// A minimal `ir.json`-shaped fixture: one lineage-capable aggregate
|
|
@@ -1592,6 +1748,7 @@ mod tests {
|
|
|
1592
1748
|
domain: "Acme".to_string(),
|
|
1593
1749
|
era: Some(1),
|
|
1594
1750
|
mirrored: None,
|
|
1751
|
+
seal_every: None,
|
|
1595
1752
|
};
|
|
1596
1753
|
{
|
|
1597
1754
|
let guard = db.lock().await;
|