hecks 2.7.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/exe/hecks +13 -0
- data/lib/hecks/adapters/driven/memory.rb +1 -1
- data/lib/hecks/adapters/driven/postgres.rb +1 -1
- data/lib/hecks/adapters/driven/prism.rb +1 -1
- data/lib/hecks/adapters/driven/sqlite/projection.rb +1 -1
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +31 -10
- data/lib/hecks/behaviors/expectations.rb +2 -2
- data/lib/hecks/bluebook/assembly/contract.rb +1 -1
- data/lib/hecks/bluebook/assembly/contracts.rb +5 -5
- data/lib/hecks/bluebook/assembly/specializer.rb +2 -2
- data/lib/hecks/bluebook/assembly.rb +1 -1
- data/lib/hecks/bluebook/behaviour/chapter.rb +5 -2
- data/lib/hecks/bluebook/behaviour/read_model.rb +18 -0
- data/lib/hecks/bluebook/capabilities.rb +2 -2
- data/lib/hecks/bluebook/chapter.rb +1 -1
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +2 -2
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +6 -1
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +2 -2
- data/lib/hecks/bluebook/dsl/entity_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +1 -1
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +14 -9
- data/lib/hecks/bluebook/dsl/rule_reference.rb +3 -3
- data/lib/hecks/bluebook/dsl/translation_builder.rb +1 -1
- data/lib/hecks/bluebook/dsl/world_builder.rb +5 -0
- data/lib/hecks/bluebook/meta_validator/judge.rb +3 -3
- data/lib/hecks/bluebook/meta_validator/plan.rb +4 -4
- data/lib/hecks/bluebook/meta_validator/readings.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/shapes.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +11 -10
- data/lib/hecks/bluebook/meta_validator.rb +1 -1
- data/lib/hecks/bluebook/model_check/client_profile.rb +4 -70
- data/lib/hecks/bluebook/model_check.rb +12 -13
- data/lib/hecks/cache_dir.rb +103 -0
- data/lib/hecks/cli/document.rb +104 -0
- data/lib/hecks/cli/ir.rb +47 -0
- data/lib/hecks/cli/mcp.rb +32 -0
- data/lib/hecks/cli/mcp_door.rb +461 -0
- data/lib/hecks/cli/model_check.rb +270 -0
- data/lib/hecks/cli/project_cli.rb +123 -0
- data/lib/hecks/cli/project_diagrams.rb +43 -0
- data/lib/hecks/cli/run.rb +219 -0
- data/lib/hecks/cli/smoke_test.rb +59 -0
- data/lib/hecks/cli/stores.rb +54 -0
- data/lib/hecks/cli.rb +166 -0
- data/lib/hecks/facade/handle.rb +2 -2
- data/lib/hecks/facade/surface/aggregate_door.rb +1 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +16 -0
- data/lib/hecks/mcp_door_scope.rb +176 -0
- data/lib/hecks/ports/persistence/append_only.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +12 -6
- data/lib/hecks/ports/persistence/state_codec.rb +1 -1
- data/lib/hecks/projections/deploy/fargate.rb +1 -1
- data/lib/hecks/projections/deploy/lambda.rb +3 -3
- data/lib/hecks/projections/reference.rb +7 -1
- data/lib/hecks/projector/exporter.rb +4 -4
- data/lib/hecks/projector/target.rb +1 -1
- data/lib/hecks/release/runner/ci_publisher.rb +96 -0
- data/lib/hecks/release/runner/commands.rb +68 -0
- data/lib/hecks/release/runner/console.rb +52 -0
- data/lib/hecks/release/runner/gem_publisher.rb +44 -0
- data/lib/hecks/release/runner/git.rb +48 -0
- data/lib/hecks/release/runner/npm_publisher.rb +89 -0
- data/lib/hecks/release/runner/preflight.rb +119 -0
- data/lib/hecks/release/runner/published.rb +55 -0
- data/lib/hecks/release/runner/tagger.rb +87 -0
- data/lib/hecks/release/runner.rb +244 -0
- data/lib/hecks/router/namespace_installer.rb +1 -1
- data/lib/hecks/runtime/command_interpreter.rb +3 -3
- data/lib/hecks/runtime/command_rules/admissibility.rb +2 -2
- data/lib/hecks/runtime/entity_element.rb +1 -1
- data/lib/hecks/runtime/entity_interpreter.rb +1 -1
- data/lib/hecks/runtime/invocation.rb +1 -1
- data/lib/hecks/runtime/loader.rb +1 -1
- data/lib/hecks/runtime/outbox.rb +1 -1
- data/lib/hecks/runtime/reaction_invocation.rb +3 -3
- data/lib/hecks/runtime/read_model_interpreter.rb +37 -15
- data/lib/hecks/runtime/reference_hop.rb +4 -4
- data/lib/hecks/runtime/registry/verification.rb +11 -11
- data/lib/hecks/runtime/registry.rb +8 -4
- data/lib/hecks/runtime/saga_interpreter.rb +1 -1
- data/lib/hecks/runtime/value/coercion.rb +3 -3
- data/lib/hecks/runtime/value/entity_list_coercion.rb +1 -1
- data/lib/hecks/storehouse.rb +18 -12
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +5 -0
- data/lib/hecks.rb +6 -1
- metadata +28 -58
- data/lib/hecks/bench/cli.rb +0 -74
- data/lib/hecks/bench/environment.rb +0 -100
- data/lib/hecks/bench/postgres_probe.rb +0 -41
- data/lib/hecks/bench/report.rb +0 -116
- data/lib/hecks/bench/ruby_runner.rb +0 -151
- data/lib/hecks/bench/run.rb +0 -55
- data/lib/hecks/bench/rust_runner.rb +0 -165
- data/lib/hecks/bench/stats.rb +0 -75
- data/lib/hecks/bench/suite.rb +0 -172
- data/lib/hecks/bench/workload.rb +0 -137
- data/lib/hecks/bench.rb +0 -28
- data/lib/hecks/codemod.rb +0 -443
- data/lib/hecks/corpus.rb +0 -479
- data/lib/hecks/doc/reference.rb +0 -585
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
- data/lib/hecks/fuzzing/combination_miner.rb +0 -178
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
- data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
- data/lib/hecks/fuzzing/differential.rb +0 -192
- data/lib/hecks/fuzzing/domain_generator.rb +0 -871
- data/lib/hecks/fuzzing/era_boundary.rb +0 -154
- data/lib/hecks/fuzzing/form_census.rb +0 -285
- data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
- data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
- data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
- data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
- data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
- data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
- data/lib/hecks/fuzzing/properties/guards.rb +0 -398
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
- data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
- data/lib/hecks/fuzzing/properties/querying.rb +0 -260
- data/lib/hecks/fuzzing/properties.rb +0 -267
- data/lib/hecks/fuzzing/qa_settings.rb +0 -164
- data/lib/hecks/fuzzing/replay.rb +0 -930
- data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
- data/lib/hecks/fuzzing/self_consistency.rb +0 -825
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
- data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
- data/lib/hecks/fuzzing/shrinker.rb +0 -309
- data/lib/hecks/fuzzing/structural_skips.rb +0 -53
- data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
- data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
- data/lib/hecks/fuzzing/value_generator.rb +0 -340
- data/lib/hecks/fuzzing.rb +0 -23
- data/lib/hecks/grammar/evolve.rb +0 -524
- data/lib/hecks/query_ir.rb +0 -482
|
@@ -39,7 +39,7 @@ module Hecks
|
|
|
39
39
|
@event_log = []
|
|
40
40
|
@reaction_log = []
|
|
41
41
|
@saga_log = []
|
|
42
|
-
# A
|
|
42
|
+
# A declarative fact, not yet a dispatched one — `AggregateDoor#
|
|
43
43
|
# mark_sensitive` (called from a `.hecksagon` file, the same way
|
|
44
44
|
# `port`/`persisted_by` already are) appends here at
|
|
45
45
|
# hecksagon-build time; `Runtime::Loader.boot`'s own post-dispatcher
|
|
@@ -162,19 +162,23 @@ module Hecks
|
|
|
162
162
|
# @return [Bluebook::Chapter] `item`, unchanged
|
|
163
163
|
def add_bluebook(item) = @bluebooks[item.name] = item
|
|
164
164
|
|
|
165
|
-
#
|
|
165
|
+
# Provenance, side-channel — which real `.bluebook` file(s)
|
|
166
166
|
# contributed to a chapter name, never part of the exported IR (a
|
|
167
167
|
# boot-time loading fact, not a domain fact) and never Rust-mirrored
|
|
168
168
|
# (the same "additive, Ruby-only" shape `@translations` above already
|
|
169
|
-
# is). Legitimate accumulation (several files declaring the
|
|
169
|
+
# is). Legitimate accumulation (several files declaring the same
|
|
170
170
|
# chapter name on purpose — `lib/hecks/language/bluebook/*.bluebook`
|
|
171
171
|
# all open `Hecks.bluebook "Bluebook"`) pushes more than one path
|
|
172
172
|
# here too; that alone is not a problem. What this exists to let
|
|
173
173
|
# `refuse_cross_package_bluebook_merge!` (registry/verification.rb)
|
|
174
|
-
# catch is
|
|
174
|
+
# catch is two unrelated packages accumulating into the same name by
|
|
175
175
|
# coincidence — a stale vendored fork's own copy of a real gem's
|
|
176
176
|
# chapter, still reachable on the load path, silently merging its
|
|
177
177
|
# aggregates into the real one via this exact accumulation mechanism.
|
|
178
|
+
#
|
|
179
|
+
# @param name [String, Symbol] the chapter name the file contributed to
|
|
180
|
+
# @param path [String] the `.bluebook` file that declared it
|
|
181
|
+
# @return [Array<String>] every path recorded for that chapter, `path` last
|
|
178
182
|
def record_bluebook_source(name, path)
|
|
179
183
|
(@bluebook_sources[name.to_s] ||= []) << path
|
|
180
184
|
end
|
|
@@ -425,7 +425,7 @@ module Hecks
|
|
|
425
425
|
#
|
|
426
426
|
# A compensation that is itself refused does not unwind again, and needs no
|
|
427
427
|
# flag to stop it: the state moves to the compensating leg's to_state before
|
|
428
|
-
# its dispatches run, so a second refusal finds the instance
|
|
428
|
+
# its dispatches run, so a second refusal finds the instance already out of
|
|
429
429
|
# from_state and records that instead. The check is the guard.
|
|
430
430
|
def unwind(process_manager, event, instance, correlation, domain)
|
|
431
431
|
return unless instance && process_manager.handles?(REFUSED)
|
|
@@ -916,11 +916,11 @@ module Hecks
|
|
|
916
916
|
# C3.8 — the boundary check for an attribute whose type is a bare
|
|
917
917
|
# primitive rather than a value object: `Integer`/`Float` by exact
|
|
918
918
|
# numeric class (`NUMERIC`), `String`/booleans by rejecting only a
|
|
919
|
-
# composite shape (`COMPOSITE_SHAPES`) —
|
|
919
|
+
# composite shape (`COMPOSITE_SHAPES`) — a looser test than
|
|
920
920
|
# `check_scalar_shapes` holds a value object's own `String` field to
|
|
921
|
-
#
|
|
921
|
+
# (QualityControl BUG#125 has that one also refuse
|
|
922
922
|
# a non-string scalar; a bare `String` argument here still admits
|
|
923
|
-
# any other scalar
|
|
923
|
+
# any other scalar — a bare-primitive
|
|
924
924
|
# attribute was never part of BUG#125's own investigation or fix,
|
|
925
925
|
# and whether it needs the same tightening, and against what real
|
|
926
926
|
# Judge dependency if any, is still open); worded by the same
|
|
@@ -27,7 +27,7 @@ module Hecks
|
|
|
27
27
|
# because coercion has to resolve value objects, and only the
|
|
28
28
|
# root answers `.value_object` at all (Entity's own header
|
|
29
29
|
# comment: an entity must not answer to it, or `Value.
|
|
30
|
-
# for_attribute` could
|
|
30
|
+
# for_attribute` could not tell a piece from a head). So
|
|
31
31
|
# a nested entity — Dispatch, inside Handler — is not a direct
|
|
32
32
|
# child of the root the way Handler itself is, and a plain
|
|
33
33
|
# `aggregate.entities.find` stops one level short of it.
|
data/lib/hecks/storehouse.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
require "fileutils"
|
|
3
3
|
require "time"
|
|
4
|
+
require_relative "cache_dir"
|
|
4
5
|
require_relative "facade/command_request"
|
|
5
6
|
require_relative "facade/json_door"
|
|
6
7
|
require_relative "naming"
|
|
@@ -8,7 +9,7 @@ require_relative "projector"
|
|
|
8
9
|
require_relative "runtime/errors"
|
|
9
10
|
|
|
10
11
|
module Hecks
|
|
11
|
-
# **The bus, not a door** — `docs/hecks-survey-what-we-wish-we-had.md` and
|
|
12
|
+
# **The bus, not a door** — `docs/archive/hecks-survey-what-we-wish-we-had.md` and
|
|
12
13
|
# `docs/future-features.md` both name the sibling project's own
|
|
13
14
|
# "Storehouse" the single highest-priority gap this repo had: "no
|
|
14
15
|
# per-command tool... the bluebook is the contract, the [door] just
|
|
@@ -90,19 +91,24 @@ module Hecks
|
|
|
90
91
|
module Storehouse
|
|
91
92
|
module_function
|
|
92
93
|
|
|
93
|
-
# The same closed set `docs/hecks-survey-what-we-wish-we-had.md`'s
|
|
94
|
+
# The same closed set `docs/archive/hecks-survey-what-we-wish-we-had.md`'s
|
|
94
95
|
# `SourceTag` names — who dispatched, not what. Optional: a caller
|
|
95
96
|
# that omits it gets `source: nil` recorded, honestly, rather than a
|
|
96
97
|
# guessed default.
|
|
97
98
|
SOURCE_TAGS = %w[process-manager operator hook sidequest-agent cascade daemon].freeze
|
|
98
99
|
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
|
|
100
|
+
# Names the directory holding the bus's own audit trail.
|
|
101
|
+
#
|
|
102
|
+
# The trail is a JSONL file per domain, one line per
|
|
103
|
+
# `dispatch`/`query`/`state`/dry-run call, and `follow` tails it back.
|
|
104
|
+
# It is kept under `Hecks::CacheDir`, not the domain's own directory: this is
|
|
105
|
+
# the bus's record of what was asked of it, not part of the domain's own
|
|
106
|
+
# persisted state, and not the gem's own directory either, which is
|
|
107
|
+
# read-only on an installed gem.
|
|
108
|
+
#
|
|
109
|
+
# @return [String] the absolute path of the `storehouse` directory under
|
|
110
|
+
# the cache root
|
|
111
|
+
def log_root = Hecks::CacheDir.path("storehouse")
|
|
106
112
|
|
|
107
113
|
# The root every `domain:`/`under:` must resolve under — the project
|
|
108
114
|
# directory by default, `HECKS_STOREHOUSE_ROOT` to widen or move it.
|
|
@@ -321,9 +327,9 @@ module Hecks
|
|
|
321
327
|
#
|
|
322
328
|
# @param domain_name [String, Symbol, #to_s] the domain name
|
|
323
329
|
# @return [String] the absolute path to that domain's JSONL log file, under
|
|
324
|
-
# `
|
|
330
|
+
# `log_root`
|
|
325
331
|
def log_path(domain_name)
|
|
326
|
-
File.join(
|
|
332
|
+
File.join(log_root, "#{domain_name.to_s.gsub(/[^A-Za-z0-9_-]/, '_')}.jsonl")
|
|
327
333
|
end
|
|
328
334
|
|
|
329
335
|
# Appends one call's outcome to its domain's audit log.
|
|
@@ -351,7 +357,7 @@ module Hecks
|
|
|
351
357
|
entry = { time: Time.now.utc.iso8601, tool: tool, verb: verb, summary: summary, source: source,
|
|
352
358
|
role: role, actor_id: actor_id,
|
|
353
359
|
ok: outcome[:ok], id: outcome[:id], error: outcome[:error], events: outcome[:events] }.compact
|
|
354
|
-
FileUtils.mkdir_p(
|
|
360
|
+
FileUtils.mkdir_p(log_root)
|
|
355
361
|
File.open(log_path(domain_name), "a") { |f| f.puts(JSON.generate(entry)) }
|
|
356
362
|
rescue StandardError
|
|
357
363
|
nil
|
data/lib/hecks/version.rb
CHANGED
data/lib/hecks/vocabulary.rb
CHANGED
|
@@ -254,6 +254,10 @@ module Hecks
|
|
|
254
254
|
{"refusal"=>"InvariantViolation", "site"=>"admits_declared_set", "argument"=>"offered", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
|
|
255
255
|
{"refusal"=>"InvariantViolation", "site"=>"undeclared_set", "argument"=>"name", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
|
|
256
256
|
{"refusal"=>"InvariantViolation", "site"=>"undeclared_set", "argument"=>"admits", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
|
|
257
|
+
{"refusal"=>"InvariantViolation", "site"=>"group_by_collision", "argument"=>"read_model", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
|
|
258
|
+
{"refusal"=>"InvariantViolation", "site"=>"group_by_collision", "argument"=>"fields", "shape"=>"list", "quoting"=>"none", "separator"=>", ", "sorted"=>"false", "when_empty"=>""}.freeze,
|
|
259
|
+
{"refusal"=>"InvariantViolation", "site"=>"group_by_collision", "argument"=>"ids", "shape"=>"list", "quoting"=>"inspect", "separator"=>", ", "sorted"=>"true", "when_empty"=>""}.freeze,
|
|
260
|
+
{"refusal"=>"InvariantViolation", "site"=>"group_by_collision", "argument"=>"key", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
|
|
257
261
|
{"refusal"=>"Unauthorized", "site"=>"tenant_required", "argument"=>"query", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
|
|
258
262
|
{"refusal"=>"Unauthorized", "site"=>"tenant_required", "argument"=>"field", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
|
|
259
263
|
{"refusal"=>"Unauthorized", "site"=>"role_mismatch", "argument"=>"command", "shape"=>"scalar", "quoting"=>"none", "separator"=>"", "sorted"=>"false", "when_empty"=>""}.freeze,
|
|
@@ -314,6 +318,7 @@ module Hecks
|
|
|
314
318
|
{"refusal"=>"InvariantViolation", "site"=>"value_object_invariant", "template"=>"{name} invariant violated — {description} (given {offered})"}.freeze,
|
|
315
319
|
{"refusal"=>"InvariantViolation", "site"=>"admits_declared_set", "template"=>"{name} admits {admits} — {admitted} — got {offered}"}.freeze,
|
|
316
320
|
{"refusal"=>"InvariantViolation", "site"=>"undeclared_set", "template"=>"{name} admits {admits}, which this chapter does not declare — a closed set is named Aggregate::SetName, and it must be one the bluebook actually holds"}.freeze,
|
|
321
|
+
{"refusal"=>"InvariantViolation", "site"=>"group_by_collision", "template"=>"{read_model} groups by {fields}, but rows {ids} share {key} — a group_by leaf holds one row; add a field that tells them apart"}.freeze,
|
|
317
322
|
{"refusal"=>"Unauthorized", "site"=>"tenant_required", "template"=>"{query} declares authorize with tenant: {field} — pass {field}: to name which {field} this ask is scoped to"}.freeze,
|
|
318
323
|
{"refusal"=>"Unauthorized", "site"=>"role_mismatch", "template"=>"{command} refused — role: {role}, and the caller stated {caller_role}"}.freeze,
|
|
319
324
|
{"refusal"=>"Unauthorized", "site"=>"cross_tenant_reference", "template"=>"{aggregate} {field} is {tenant}, but {attribute} names a {target} whose own {target_field} is {other} — a cross-tenant reference"}.freeze,
|
data/lib/hecks.rb
CHANGED
|
@@ -32,11 +32,16 @@ require_relative "hecks/facade/cli_door"
|
|
|
32
32
|
require_relative "hecks/facade/cli_runner"
|
|
33
33
|
require_relative "hecks/storehouse"
|
|
34
34
|
require_relative "hecks/mcp_stdio_guard"
|
|
35
|
+
require_relative "hecks/mcp_door_scope"
|
|
35
36
|
require_relative "hecks/framework"
|
|
36
|
-
require_relative "hecks/corpus"
|
|
37
37
|
require_relative "hecks/vendoring"
|
|
38
38
|
require_relative "hecks/embryonaut_bluebook"
|
|
39
39
|
|
|
40
|
+
# Repository-only tooling: the corpus table walks `examples/`, `qa/` and
|
|
41
|
+
# `spec/`, which only a checkout has, so it loads on first use and the
|
|
42
|
+
# packaged gem leaves it out (ADR 0066).
|
|
43
|
+
Hecks.autoload(:Corpus, File.expand_path("hecks/corpus", __dir__))
|
|
44
|
+
|
|
40
45
|
# The root namespace and public facade of the whole DSL/runtime: `Hecks.boot`/
|
|
41
46
|
# `.boot_files` assemble a running domain from `.bluebook`/`.hecksagon`/
|
|
42
47
|
# `.world` files, and `.bluebook`/`.hecksagon`/`.port`/`.adapter`/`.world`/
|
metadata
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hecks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chris Young
|
|
8
8
|
autorequire:
|
|
9
|
-
bindir:
|
|
9
|
+
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
11
|
date: 2026-09-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
@@ -30,10 +30,12 @@ description: |
|
|
|
30
30
|
Nothing is scripted: no handler body, no class you write, no schema
|
|
31
31
|
you migrate.
|
|
32
32
|
email:
|
|
33
|
-
executables:
|
|
33
|
+
executables:
|
|
34
|
+
- hecks
|
|
34
35
|
extensions: []
|
|
35
36
|
extra_rdoc_files: []
|
|
36
37
|
files:
|
|
38
|
+
- exe/hecks
|
|
37
39
|
- lib/hecks.rb
|
|
38
40
|
- lib/hecks/adapters.rb
|
|
39
41
|
- lib/hecks/adapters/driven.rb
|
|
@@ -94,17 +96,6 @@ files:
|
|
|
94
96
|
- lib/hecks/behaviors/ir.rb
|
|
95
97
|
- lib/hecks/behaviors/rspec.rb
|
|
96
98
|
- lib/hecks/behaviors/runner.rb
|
|
97
|
-
- lib/hecks/bench.rb
|
|
98
|
-
- lib/hecks/bench/cli.rb
|
|
99
|
-
- lib/hecks/bench/environment.rb
|
|
100
|
-
- lib/hecks/bench/postgres_probe.rb
|
|
101
|
-
- lib/hecks/bench/report.rb
|
|
102
|
-
- lib/hecks/bench/ruby_runner.rb
|
|
103
|
-
- lib/hecks/bench/run.rb
|
|
104
|
-
- lib/hecks/bench/rust_runner.rb
|
|
105
|
-
- lib/hecks/bench/stats.rb
|
|
106
|
-
- lib/hecks/bench/suite.rb
|
|
107
|
-
- lib/hecks/bench/workload.rb
|
|
108
99
|
- lib/hecks/bluebook.rb
|
|
109
100
|
- lib/hecks/bluebook/aggregate.rb
|
|
110
101
|
- lib/hecks/bluebook/assembly.rb
|
|
@@ -199,13 +190,22 @@ files:
|
|
|
199
190
|
- lib/hecks/bluebook/synthesizer.rb
|
|
200
191
|
- lib/hecks/bluebook/translation.rb
|
|
201
192
|
- lib/hecks/bluebook/value_object.rb
|
|
202
|
-
- lib/hecks/
|
|
193
|
+
- lib/hecks/cache_dir.rb
|
|
194
|
+
- lib/hecks/cli.rb
|
|
195
|
+
- lib/hecks/cli/document.rb
|
|
196
|
+
- lib/hecks/cli/ir.rb
|
|
197
|
+
- lib/hecks/cli/mcp.rb
|
|
198
|
+
- lib/hecks/cli/mcp_door.rb
|
|
199
|
+
- lib/hecks/cli/model_check.rb
|
|
200
|
+
- lib/hecks/cli/project_cli.rb
|
|
201
|
+
- lib/hecks/cli/project_diagrams.rb
|
|
202
|
+
- lib/hecks/cli/run.rb
|
|
203
|
+
- lib/hecks/cli/smoke_test.rb
|
|
204
|
+
- lib/hecks/cli/stores.rb
|
|
203
205
|
- lib/hecks/construct.rb
|
|
204
|
-
- lib/hecks/corpus.rb
|
|
205
206
|
- lib/hecks/deploy/bluebook/deploy.bluebook
|
|
206
207
|
- lib/hecks/deploy/bluebook/deploy.hecksagon
|
|
207
208
|
- lib/hecks/deploy/oidc.json
|
|
208
|
-
- lib/hecks/doc/reference.rb
|
|
209
209
|
- lib/hecks/embryonaut_bluebook.rb
|
|
210
210
|
- lib/hecks/embryonaut_bluebook/lock.rb
|
|
211
211
|
- lib/hecks/embryonaut_bluebook/shape.rb
|
|
@@ -246,47 +246,7 @@ files:
|
|
|
246
246
|
- lib/hecks/framework/bluebook/privacy.bluebook
|
|
247
247
|
- lib/hecks/framework/oidc.json
|
|
248
248
|
- lib/hecks/freezer.rb
|
|
249
|
-
- lib/hecks/fuzzing.rb
|
|
250
|
-
- lib/hecks/fuzzing/bounded_exhaustive_expressions.rb
|
|
251
|
-
- lib/hecks/fuzzing/combination_miner.rb
|
|
252
|
-
- lib/hecks/fuzzing/concurrent_dispatch.rb
|
|
253
|
-
- lib/hecks/fuzzing/coverage_campaign.rb
|
|
254
|
-
- lib/hecks/fuzzing/differential.rb
|
|
255
|
-
- lib/hecks/fuzzing/domain_generator.rb
|
|
256
|
-
- lib/hecks/fuzzing/era_boundary.rb
|
|
257
|
-
- lib/hecks/fuzzing/form_census.rb
|
|
258
|
-
- lib/hecks/fuzzing/generated_domain_check.rb
|
|
259
|
-
- lib/hecks/fuzzing/invalid_value_generator.rb
|
|
260
|
-
- lib/hecks/fuzzing/isolated_boot.rb
|
|
261
|
-
- lib/hecks/fuzzing/nondeterministic.rb
|
|
262
|
-
- lib/hecks/fuzzing/persistence_parity.rb
|
|
263
|
-
- lib/hecks/fuzzing/properties.rb
|
|
264
|
-
- lib/hecks/fuzzing/properties/corrections.rb
|
|
265
|
-
- lib/hecks/fuzzing/properties/dispatch_and_mutations.rb
|
|
266
|
-
- lib/hecks/fuzzing/properties/guards.rb
|
|
267
|
-
- lib/hecks/fuzzing/properties/invariants_and_aggregation.rb
|
|
268
|
-
- lib/hecks/fuzzing/properties/lifecycle_and_replay.rb
|
|
269
|
-
- lib/hecks/fuzzing/properties/outbox.rb
|
|
270
|
-
- lib/hecks/fuzzing/properties/querying.rb
|
|
271
|
-
- lib/hecks/fuzzing/qa_settings.rb
|
|
272
|
-
- lib/hecks/fuzzing/replay.rb
|
|
273
|
-
- lib/hecks/fuzzing/rotation_priority.rb
|
|
274
|
-
- lib/hecks/fuzzing/rust_gap_manifest.rb
|
|
275
|
-
- lib/hecks/fuzzing/self_consistency.rb
|
|
276
|
-
- lib/hecks/fuzzing/sequence_generator.rb
|
|
277
|
-
- lib/hecks/fuzzing/sequence_generator/adversary.rb
|
|
278
|
-
- lib/hecks/fuzzing/sequence_generator/catalog.rb
|
|
279
|
-
- lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb
|
|
280
|
-
- lib/hecks/fuzzing/sequence_generator/picker.rb
|
|
281
|
-
- lib/hecks/fuzzing/sequence_generator/query_binding.rb
|
|
282
|
-
- lib/hecks/fuzzing/sequence_generator/step_builder.rb
|
|
283
|
-
- lib/hecks/fuzzing/shrinker.rb
|
|
284
|
-
- lib/hecks/fuzzing/structural_skips.rb
|
|
285
|
-
- lib/hecks/fuzzing/sweep_depth.rb
|
|
286
|
-
- lib/hecks/fuzzing/target_capabilities.rb
|
|
287
|
-
- lib/hecks/fuzzing/value_generator.rb
|
|
288
249
|
- lib/hecks/grammar.rb
|
|
289
|
-
- lib/hecks/grammar/evolve.rb
|
|
290
250
|
- lib/hecks/grammar/expression.bluebook
|
|
291
251
|
- lib/hecks/grammar/expression_operators.json
|
|
292
252
|
- lib/hecks/grammar/grammar.hecksagon
|
|
@@ -318,6 +278,7 @@ files:
|
|
|
318
278
|
- lib/hecks/language/world/wiring.bluebook
|
|
319
279
|
- lib/hecks/language/world/world.bluebook
|
|
320
280
|
- lib/hecks/literal.rb
|
|
281
|
+
- lib/hecks/mcp_door_scope.rb
|
|
321
282
|
- lib/hecks/mcp_stdio_guard.rb
|
|
322
283
|
- lib/hecks/naming.rb
|
|
323
284
|
- lib/hecks/ports.rb
|
|
@@ -460,7 +421,6 @@ files:
|
|
|
460
421
|
- lib/hecks/projector/ir_projector.rb
|
|
461
422
|
- lib/hecks/projector/narrate_projector.rb
|
|
462
423
|
- lib/hecks/projector/target.rb
|
|
463
|
-
- lib/hecks/query_ir.rb
|
|
464
424
|
- lib/hecks/query_specification.rb
|
|
465
425
|
- lib/hecks/query_specification/common/authorization_spec.rb
|
|
466
426
|
- lib/hecks/query_specification/common/comparators.rb
|
|
@@ -481,6 +441,16 @@ files:
|
|
|
481
441
|
- lib/hecks/query_specification/read_model/specification.rb
|
|
482
442
|
- lib/hecks/release/gem_pin.rb
|
|
483
443
|
- lib/hecks/release/gem_pin/published_versions.rb
|
|
444
|
+
- lib/hecks/release/runner.rb
|
|
445
|
+
- lib/hecks/release/runner/ci_publisher.rb
|
|
446
|
+
- lib/hecks/release/runner/commands.rb
|
|
447
|
+
- lib/hecks/release/runner/console.rb
|
|
448
|
+
- lib/hecks/release/runner/gem_publisher.rb
|
|
449
|
+
- lib/hecks/release/runner/git.rb
|
|
450
|
+
- lib/hecks/release/runner/npm_publisher.rb
|
|
451
|
+
- lib/hecks/release/runner/preflight.rb
|
|
452
|
+
- lib/hecks/release/runner/published.rb
|
|
453
|
+
- lib/hecks/release/runner/tagger.rb
|
|
484
454
|
- lib/hecks/rendering.rb
|
|
485
455
|
- lib/hecks/router.rb
|
|
486
456
|
- lib/hecks/router/namespace_installer.rb
|
data/lib/hecks/bench/cli.rb
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
require "optparse"
|
|
2
|
-
require_relative "suite"
|
|
3
|
-
require_relative "report"
|
|
4
|
-
|
|
5
|
-
module Hecks
|
|
6
|
-
module Bench
|
|
7
|
-
# The command line behind `bin/bench`: parses flags, runs the `Suite`, prints the report.
|
|
8
|
-
#
|
|
9
|
-
# Kept in `lib/` so a spec can drive it with a tiny configuration and read what it
|
|
10
|
-
# prints, rather than shelling out to a script.
|
|
11
|
-
module CLI
|
|
12
|
-
# The defaults `bin/bench` runs with when no flag says otherwise.
|
|
13
|
-
DEFAULTS = { domains: %w[pizzas banking], targets: Suite::TARGETS, warmup: 200, iterations: 1000,
|
|
14
|
-
runs: 3 }.freeze
|
|
15
|
-
|
|
16
|
-
module_function
|
|
17
|
-
|
|
18
|
-
# Runs the benchmark described by the command line and prints its report.
|
|
19
|
-
#
|
|
20
|
-
# @param argv [Array<String>] the command-line arguments
|
|
21
|
-
# @param out [#puts] where the report goes
|
|
22
|
-
# @param err [#puts] where progress and usage errors go
|
|
23
|
-
# @return [Integer] the process exit status: 0 on success, 2 for a usage error
|
|
24
|
-
def run(argv, out: $stdout, err: $stderr)
|
|
25
|
-
options = parse(argv)
|
|
26
|
-
result = Suite.call(options.fetch(:config), log: err)
|
|
27
|
-
out.puts(options[:format] == "json" ? Report.json(result) : Report.markdown(result))
|
|
28
|
-
File.write(options[:output], Report.json(result)) if options[:output]
|
|
29
|
-
0
|
|
30
|
-
rescue OptionParser::ParseError, ArgumentError => e
|
|
31
|
-
err.puts "bin/bench: #{e.message}"
|
|
32
|
-
2
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
# Turns command-line arguments into a configuration and output choices.
|
|
36
|
-
#
|
|
37
|
-
# @param argv [Array<String>] the command-line arguments
|
|
38
|
-
# @return [Hash{Symbol => Object}] `:config` (a `Suite::Config`), `:format` and `:output`
|
|
39
|
-
# @raise [OptionParser::ParseError] on an unknown or malformed flag
|
|
40
|
-
def parse(argv)
|
|
41
|
-
values = DEFAULTS.dup
|
|
42
|
-
extras = { format: "markdown", output: nil, rust_binary: nil }
|
|
43
|
-
parser(values, extras).parse(argv.dup)
|
|
44
|
-
{ config: Suite::Config.new(**values, rust_binary: extras[:rust_binary]),
|
|
45
|
-
format: extras[:format], output: extras[:output] }
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Builds the flag parser.
|
|
49
|
-
#
|
|
50
|
-
# @param values [Hash{Symbol => Object}] the `Suite::Config` fields, filled in as flags parse
|
|
51
|
-
# @param extras [Hash{Symbol => Object}] the output choices, filled in as flags parse
|
|
52
|
-
# @return [OptionParser] the parser
|
|
53
|
-
def parser(values, extras)
|
|
54
|
-
OptionParser.new do |opts|
|
|
55
|
-
opts.banner = "usage: bin/bench [options]"
|
|
56
|
-
opts.on("--domain NAMES", Array, "pizzas, banking (default: both)") { |v| values[:domains] = v }
|
|
57
|
-
opts.on("--targets NAMES", Array, "#{Suite::TARGETS.join(', ')} (default: all)") { |v| values[:targets] = v }
|
|
58
|
-
opts.on("--iterations N", Integer, "timed cycles per run (default #{DEFAULTS[:iterations]})") do |v|
|
|
59
|
-
values[:iterations] = v
|
|
60
|
-
end
|
|
61
|
-
opts.on("--warmup N", Integer, "discarded cycles before timing (default #{DEFAULTS[:warmup]})") do |v|
|
|
62
|
-
values[:warmup] = v
|
|
63
|
-
end
|
|
64
|
-
opts.on("--runs N", Integer, "fresh boots per target, median reported (default #{DEFAULTS[:runs]})") do |v|
|
|
65
|
-
values[:runs] = v
|
|
66
|
-
end
|
|
67
|
-
opts.on("--rust-binary PATH", "use this `rust` binary instead of building one") { |v| extras[:rust_binary] = v }
|
|
68
|
-
opts.on("--format FORMAT", %w[markdown json], "markdown (default) or json") { |v| extras[:format] = v }
|
|
69
|
-
opts.on("--output PATH", "also write the full JSON result here") { |v| extras[:output] = v }
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
require "etc"
|
|
2
|
-
require "open3"
|
|
3
|
-
|
|
4
|
-
module Hecks
|
|
5
|
-
module Bench
|
|
6
|
-
# A description of the machine and toolchain a benchmark ran on.
|
|
7
|
-
#
|
|
8
|
-
# A number without its hardware is not reproducible, so every report carries
|
|
9
|
-
# this. Each probe is best effort: a missing tool becomes `"unknown"` rather
|
|
10
|
-
# than an error, since the benchmark itself is still valid without it.
|
|
11
|
-
module Environment
|
|
12
|
-
module_function
|
|
13
|
-
|
|
14
|
-
# Collects the machine and toolchain facts a report should carry.
|
|
15
|
-
#
|
|
16
|
-
# @return [Hash{Symbol => Object}] `:cpu`, `:cores`, `:memory_gib`, `:os`, `:ruby`,
|
|
17
|
-
# `:yjit`, `:rustc`, `:cargo`, `:hecks`, `:commit` and `:load_average`
|
|
18
|
-
def describe
|
|
19
|
-
{
|
|
20
|
-
cpu: cpu, cores: Etc.nprocessors, memory_gib: memory_gib, os: os,
|
|
21
|
-
ruby: RUBY_DESCRIPTION, yjit: yjit?,
|
|
22
|
-
rustc: capture("rustc", "-V"), cargo: capture("cargo", "-V"),
|
|
23
|
-
hecks: Hecks::VERSION, commit: commit, load_average: load_average
|
|
24
|
-
}
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Reads the one-minute load average, the quickest sign that something else was
|
|
28
|
-
# busy while a benchmark ran. `Suite.call` reads it before and after and reports both.
|
|
29
|
-
#
|
|
30
|
-
# @return [Float, nil] the one-minute load average, or nil when it cannot be read
|
|
31
|
-
def load_average
|
|
32
|
-
text = RUBY_PLATFORM.include?("darwin") ? capture("sysctl", "-n", "vm.loadavg").delete("{}") : File.read("/proc/loadavg")
|
|
33
|
-
text.split.first.to_f.round(2)
|
|
34
|
-
rescue SystemCallError
|
|
35
|
-
nil
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Names the processor.
|
|
39
|
-
#
|
|
40
|
-
# @return [String] the CPU model, or `"unknown"`
|
|
41
|
-
def cpu
|
|
42
|
-
return capture("sysctl", "-n", "machdep.cpu.brand_string") if RUBY_PLATFORM.include?("darwin")
|
|
43
|
-
|
|
44
|
-
model = File.foreach("/proc/cpuinfo").find { |line| line.start_with?("model name") }
|
|
45
|
-
model ? model.split(":", 2).last.strip : "unknown"
|
|
46
|
-
rescue SystemCallError
|
|
47
|
-
"unknown"
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Reads installed memory.
|
|
51
|
-
#
|
|
52
|
-
# @return [Float, String] physical memory in GiB, or `"unknown"`
|
|
53
|
-
def memory_gib
|
|
54
|
-
bytes =
|
|
55
|
-
if RUBY_PLATFORM.include?("darwin")
|
|
56
|
-
capture("sysctl", "-n", "hw.memsize").to_i
|
|
57
|
-
else
|
|
58
|
-
File.read("/proc/meminfo")[/MemTotal:\s+(\d+)/, 1].to_i * 1024
|
|
59
|
-
end
|
|
60
|
-
bytes.zero? ? "unknown" : (bytes / (1024.0**3)).round(1)
|
|
61
|
-
rescue SystemCallError
|
|
62
|
-
"unknown"
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
# Names the operating system.
|
|
66
|
-
#
|
|
67
|
-
# @return [String] the OS name and version, or `"unknown"`
|
|
68
|
-
def os
|
|
69
|
-
return "macOS #{capture('sw_vers', '-productVersion')} (#{capture('uname', '-m')})" if RUBY_PLATFORM.include?("darwin")
|
|
70
|
-
|
|
71
|
-
capture("uname", "-sr")
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Says whether Ruby's `YJIT` compiler was running.
|
|
75
|
-
#
|
|
76
|
-
# @return [Boolean] true when `YJIT` is enabled in this process
|
|
77
|
-
def yjit?
|
|
78
|
-
defined?(RubyVM::YJIT) ? RubyVM::YJIT.enabled? : false
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
# Names the Hecks source revision under test.
|
|
82
|
-
#
|
|
83
|
-
# @return [String] the short commit hash, or `"unknown"` outside a git checkout
|
|
84
|
-
def commit
|
|
85
|
-
capture("git", "-C", File.expand_path("../../..", __dir__), "rev-parse", "--short", "HEAD")
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# Runs a command and returns its first line of output.
|
|
89
|
-
#
|
|
90
|
-
# @param command [Array<String>] the program and its arguments
|
|
91
|
-
# @return [String] the trimmed first line of stdout, or `"unknown"` if it cannot run
|
|
92
|
-
def capture(*command)
|
|
93
|
-
out, status = Open3.capture2(*command, err: File::NULL)
|
|
94
|
-
status.success? && !out.strip.empty? ? out.lines.first.strip : "unknown"
|
|
95
|
-
rescue SystemCallError
|
|
96
|
-
"unknown"
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
module Hecks
|
|
2
|
-
module Bench
|
|
3
|
-
# Decides whether the Postgres targets can run at all.
|
|
4
|
-
#
|
|
5
|
-
# Postgres is optional. A machine with no server, or without the `pg` gem, gets a
|
|
6
|
-
# skipped target and a sentence saying why, never a stack trace. Connection
|
|
7
|
-
# settings come from libpq's own environment (`PGHOST`, `PGPORT`, `PGUSER`,
|
|
8
|
-
# `PGPASSWORD`), the same way the fuzzer's Postgres adapters read them.
|
|
9
|
-
module PostgresProbe
|
|
10
|
-
module_function
|
|
11
|
-
|
|
12
|
-
# Tries to reach the server's maintenance database.
|
|
13
|
-
#
|
|
14
|
-
# @return [String, nil] nil when a server answered, otherwise one sentence saying why
|
|
15
|
-
# the Postgres targets cannot run
|
|
16
|
-
def unavailable_reason
|
|
17
|
-
require "pg"
|
|
18
|
-
PG.connect(dbname: "postgres", connect_timeout: 2).close
|
|
19
|
-
nil
|
|
20
|
-
rescue LoadError
|
|
21
|
-
"the `pg` gem is not installed"
|
|
22
|
-
rescue PG::Error => e
|
|
23
|
-
"no Postgres server is reachable (#{e.message.lines.first.to_s.strip}); start one, or point " \
|
|
24
|
-
"PGHOST, PGPORT and PGUSER at one"
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Reads the server's version for the report's environment block.
|
|
28
|
-
#
|
|
29
|
-
# @return [String] the `server_version` setting, or `"unknown"` if it cannot be read
|
|
30
|
-
def server_version
|
|
31
|
-
require "pg"
|
|
32
|
-
connection = PG.connect(dbname: "postgres", connect_timeout: 2)
|
|
33
|
-
connection.exec("SHOW server_version").getvalue(0, 0)
|
|
34
|
-
rescue LoadError, PG::Error
|
|
35
|
-
"unknown"
|
|
36
|
-
ensure
|
|
37
|
-
connection&.close
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|