textus 0.55.1 → 0.56.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/CHANGELOG.md +175 -2768
- data/README.md +17 -18
- data/lib/textus/artifact.rb +8 -0
- data/lib/textus/errors.rb +2 -39
- data/lib/textus/human.rb +7 -0
- data/lib/textus/infra/adapters/concurrency_adapter.rb +15 -0
- data/lib/textus/infra/clock.rb +7 -0
- data/lib/textus/infra/database.rb +127 -0
- data/lib/textus/infra/file_system.rb +54 -0
- data/lib/textus/infra/locks.rb +50 -0
- data/lib/textus/infra/port/clock.rb +18 -0
- data/lib/textus/infra/port/publisher.rb +71 -0
- data/lib/textus/infra/port/sentinel.rb +25 -0
- data/lib/textus/infra/port/sentinel_store.rb +93 -0
- data/lib/textus/infra/store/audit_log.rb +42 -0
- data/lib/textus/infra/store/base.rb +25 -0
- data/lib/textus/infra/store/cursor_store.rb +27 -0
- data/lib/textus/infra/store/entry_index.rb +31 -0
- data/lib/textus/infra/store/envelope/meta.rb +63 -0
- data/lib/textus/infra/store/file_store.rb +23 -0
- data/lib/textus/infra/store/index/builder.rb +28 -0
- data/lib/textus/infra/store/index/lookup.rb +23 -0
- data/lib/textus/infra/store/link_store.rb +43 -0
- data/lib/textus/infra/store/workflow_queue.rb +54 -0
- data/lib/textus/infra/store.rb +29 -0
- data/lib/textus/ingest.rb +8 -0
- data/lib/textus/lanes/artifact/cache.rb +35 -0
- data/lib/textus/lanes/artifact/handlers.rb +29 -0
- data/lib/textus/lanes/artifact/lifecycle.rb +23 -0
- data/lib/textus/lanes/artifact/verbs.rb +18 -0
- data/lib/textus/lanes/ingest/dedup.rb +17 -0
- data/lib/textus/lanes/ingest/entry_types/asset.rb +45 -0
- data/lib/textus/lanes/ingest/entry_types/link.rb +16 -0
- data/lib/textus/lanes/ingest/entry_types/text.rb +17 -0
- data/lib/textus/lanes/ingest/entry_types.rb +25 -0
- data/lib/textus/lanes/ingest/handlers.rb +54 -0
- data/lib/textus/lanes/ingest/index_rebuilder.rb +13 -0
- data/lib/textus/lanes/ingest/key_builder.rb +33 -0
- data/lib/textus/lanes/ingest/resolver.rb +21 -0
- data/lib/textus/lanes/knowledge/freeform.rb +15 -0
- data/lib/textus/lanes/knowledge/handlers.rb +53 -0
- data/lib/textus/lanes/knowledge/runner.rb +45 -0
- data/lib/textus/lanes/knowledge/verbs.rb +16 -0
- data/lib/textus/lanes/scratchpad/handlers.rb +164 -0
- data/lib/textus/lanes/scratchpad/verbs.rb +73 -0
- data/lib/textus/{gate → protocol}/binder.rb +2 -10
- data/lib/textus/protocol/boot/catalog.rb +34 -0
- data/lib/textus/protocol/boot/context.rb +124 -0
- data/lib/textus/protocol/boot/protocol.rb +33 -0
- data/lib/textus/protocol/boot.rb +42 -0
- data/lib/textus/protocol/builder/domain.rb +17 -0
- data/lib/textus/protocol/builder/infrastructure.rb +17 -0
- data/lib/textus/protocol/builder/runtime.rb +46 -0
- data/lib/textus/protocol/builder.rb +11 -0
- data/lib/textus/protocol/container.rb +22 -0
- data/lib/textus/protocol/diff.rb +170 -0
- data/lib/textus/protocol/entry_constraint.rb +54 -0
- data/lib/textus/protocol/envelope.rb +70 -0
- data/lib/textus/protocol/format/base.rb +80 -0
- data/lib/textus/protocol/format/bash.rb +10 -0
- data/lib/textus/protocol/format/javascript.rb +10 -0
- data/lib/textus/protocol/format/json.rb +82 -0
- data/lib/textus/protocol/format/markdown.rb +53 -0
- data/lib/textus/protocol/format/python.rb +10 -0
- data/lib/textus/protocol/format/ruby.rb +10 -0
- data/lib/textus/protocol/format/script.rb +55 -0
- data/lib/textus/protocol/format/text.rb +48 -0
- data/lib/textus/protocol/format/yaml.rb +105 -0
- data/lib/textus/protocol/format.rb +73 -0
- data/lib/textus/protocol/freshness/ttl_evaluator.rb +61 -0
- data/lib/textus/protocol/gate.rb +61 -0
- data/lib/textus/protocol/handlers/command_context.rb +19 -0
- data/lib/textus/protocol/handlers/concern.rb +15 -0
- data/lib/textus/protocol/handlers/ops/audit_filter.rb +37 -0
- data/lib/textus/protocol/handlers/ops/bulk_planner.rb +38 -0
- data/lib/textus/protocol/handlers/ops/data_mv.rb +49 -0
- data/lib/textus/protocol/handlers/ops/git_blame.rb +65 -0
- data/lib/textus/protocol/handlers/ops/pulse_coordinator.rb +37 -0
- data/lib/textus/protocol/handlers/ops/source_expander.rb +41 -0
- data/lib/textus/protocol/handlers/query_context.rb +41 -0
- data/lib/textus/protocol/handlers/read.rb +67 -0
- data/lib/textus/protocol/handlers/system.rb +93 -0
- data/lib/textus/protocol/handlers/write.rb +57 -0
- data/lib/textus/protocol/init.rb +143 -0
- data/lib/textus/protocol/key/grammar.rb +35 -0
- data/lib/textus/protocol/key/matching.rb +23 -0
- data/lib/textus/protocol/key/path.rb +23 -0
- data/lib/textus/protocol/key.rb +25 -0
- data/lib/textus/protocol/lane.rb +12 -0
- data/lib/textus/{store/geometry.rb → protocol/layout.rb} +32 -22
- data/lib/textus/protocol/links/resolver.rb +33 -0
- data/lib/textus/protocol/links/uri_rewriter.rb +30 -0
- data/lib/textus/protocol/links.rb +10 -0
- data/lib/textus/protocol/manifest/data.rb +90 -0
- data/lib/textus/protocol/manifest/entry/base.rb +95 -0
- data/lib/textus/protocol/manifest/entry/leaf.rb +19 -0
- data/lib/textus/protocol/manifest/entry/nested.rb +19 -0
- data/lib/textus/protocol/manifest/entry/parser.rb +71 -0
- data/lib/textus/protocol/manifest/entry/publish.rb +209 -0
- data/lib/textus/protocol/manifest/entry.rb +10 -0
- data/lib/textus/protocol/manifest/policy/publish_target.rb +36 -0
- data/lib/textus/protocol/manifest/policy.rb +69 -0
- data/lib/textus/protocol/manifest/resolver.rb +156 -0
- data/lib/textus/protocol/manifest/schema/contract.rb +53 -0
- data/lib/textus/protocol/manifest/schema/validator.rb +52 -0
- data/lib/textus/protocol/manifest/schema.rb +40 -0
- data/lib/textus/protocol/manifest.rb +62 -0
- data/lib/textus/protocol/produce/render/context.rb +43 -0
- data/lib/textus/protocol/produce/render.rb +44 -0
- data/lib/textus/protocol/schema/registry.rb +44 -0
- data/lib/textus/protocol/schema/tools.rb +104 -0
- data/lib/textus/protocol/schema.rb +117 -0
- data/lib/textus/protocol/session/bridge.rb +11 -0
- data/lib/textus/protocol/session/context.rb +5 -0
- data/lib/textus/protocol/session.rb +87 -0
- data/lib/textus/protocol/store_engine/delete.rb +37 -0
- data/lib/textus/protocol/store_engine/move.rb +109 -0
- data/lib/textus/protocol/store_engine/pipeline.rb +61 -0
- data/lib/textus/protocol/store_engine/put.rb +104 -0
- data/lib/textus/protocol/store_engine.rb +86 -0
- data/lib/textus/protocol/v4.rb +25 -0
- data/lib/textus/protocol/verb_registry/arg_spec.rb +19 -0
- data/lib/textus/protocol/verb_registry/core_verbs.rb +164 -0
- data/lib/textus/protocol/verb_registry/verb_spec.rb +42 -0
- data/lib/textus/protocol/verb_registry.rb +42 -0
- data/lib/textus/surface/cli/group/{rule.rb → web.rb} +2 -2
- data/lib/textus/surface/cli/runner.rb +25 -25
- data/lib/textus/surface/cli/sources.rb +1 -3
- data/lib/textus/surface/cli/verb/get.rb +3 -6
- data/lib/textus/surface/cli/verb/init.rb +1 -1
- data/lib/textus/surface/cli/verb/put.rb +8 -6
- data/lib/textus/surface/cli/verb/web_serve.rb +22 -0
- data/lib/textus/surface/cli.rb +25 -6
- data/lib/textus/surface/dispatch.rb +17 -0
- data/lib/textus/surface/mcp/adapters/mcp_adapter.rb +28 -0
- data/lib/textus/surface/mcp/catalog.rb +26 -62
- data/lib/textus/surface/mcp/cursor_manager.rb +22 -0
- data/lib/textus/surface/mcp/drift_annotation.rb +19 -0
- data/lib/textus/surface/mcp/errors.rb +0 -10
- data/lib/textus/surface/mcp/server.rb +46 -46
- data/lib/textus/surface/watcher.rb +18 -12
- data/lib/textus/surface/web.rb +50 -0
- data/lib/textus/value/call.rb +1 -1
- data/lib/textus/{core → value}/duration.rb +1 -4
- data/lib/textus/value/etag.rb +7 -7
- data/lib/textus/value/payload.rb +7 -0
- data/lib/textus/version.rb +1 -1
- data/lib/textus/workflow/consumer.rb +39 -0
- data/lib/textus/workflow/dsl/lifecycle.rb +32 -0
- data/lib/textus/workflow/dsl/pattern_matcher.rb +20 -0
- data/lib/textus/workflow/dsl/step_builder.rb +56 -0
- data/lib/textus/workflow/dsl.rb +111 -21
- data/lib/textus/workflow/event_emitter.rb +21 -0
- data/lib/textus/workflow/publisher.rb +59 -0
- data/lib/textus/workflow/registry.rb +31 -8
- data/lib/textus/workflow/runner.rb +25 -61
- data/lib/textus/workflow/scheduler.rb +45 -0
- data/lib/textus/workflow/step_executor.rb +78 -0
- data/lib/textus/workflow/step_helpers.rb +102 -0
- data/lib/textus/workflow.rb +2 -5
- data/lib/textus.rb +16 -80
- metadata +167 -183
- data/SPEC.md +0 -908
- data/docs/architecture/README.md +0 -273
- data/docs/reference/conventions.md +0 -138
- data/lib/textus/action/accept.rb +0 -46
- data/lib/textus/action/audit.rb +0 -94
- data/lib/textus/action/base.rb +0 -42
- data/lib/textus/action/blame.rb +0 -79
- data/lib/textus/action/boot.rb +0 -15
- data/lib/textus/action/data_mv.rb +0 -58
- data/lib/textus/action/deps.rb +0 -19
- data/lib/textus/action/doctor.rb +0 -17
- data/lib/textus/action/drain.rb +0 -31
- data/lib/textus/action/enqueue.rb +0 -37
- data/lib/textus/action/get.rb +0 -34
- data/lib/textus/action/ingest.rb +0 -199
- data/lib/textus/action/jobs.rb +0 -27
- data/lib/textus/action/key_delete.rb +0 -26
- data/lib/textus/action/key_delete_prefix.rb +0 -35
- data/lib/textus/action/key_mv.rb +0 -122
- data/lib/textus/action/key_mv_prefix.rb +0 -48
- data/lib/textus/action/list.rb +0 -28
- data/lib/textus/action/propose.rb +0 -42
- data/lib/textus/action/published.rb +0 -22
- data/lib/textus/action/pulse.rb +0 -49
- data/lib/textus/action/put.rb +0 -38
- data/lib/textus/action/rdeps.rb +0 -24
- data/lib/textus/action/reject.rb +0 -28
- data/lib/textus/action/rule_explain.rb +0 -81
- data/lib/textus/action/rule_lint.rb +0 -62
- data/lib/textus/action/rule_list.rb +0 -38
- data/lib/textus/action/schema_envelope.rb +0 -22
- data/lib/textus/action/uid.rb +0 -19
- data/lib/textus/action/where.rb +0 -21
- data/lib/textus/boot.rb +0 -202
- data/lib/textus/contract/arg.rb +0 -10
- data/lib/textus/contract/dsl.rb +0 -88
- data/lib/textus/contract/spec.rb +0 -25
- data/lib/textus/contract.rb +0 -12
- data/lib/textus/core/freshness/evaluator.rb +0 -150
- data/lib/textus/core/freshness/verdict.rb +0 -34
- data/lib/textus/core/freshness.rb +0 -11
- data/lib/textus/core/retention/sweep.rb +0 -57
- data/lib/textus/core/retention.rb +0 -11
- data/lib/textus/core/sentinel.rb +0 -30
- data/lib/textus/doctor/check/audit_log.rb +0 -34
- data/lib/textus/doctor/check/generator_drift.rb +0 -29
- data/lib/textus/doctor/check/illegal_keys.rb +0 -60
- data/lib/textus/doctor/check/manifest_files.rb +0 -25
- data/lib/textus/doctor/check/notebook_sources.rb +0 -50
- data/lib/textus/doctor/check/orphaned_publish_targets.rb +0 -35
- data/lib/textus/doctor/check/proposal_targets.rb +0 -45
- data/lib/textus/doctor/check/protocol_version.rb +0 -47
- data/lib/textus/doctor/check/publish_tree_index_overlap.rb +0 -48
- data/lib/textus/doctor/check/raw_asset_paths.rb +0 -50
- data/lib/textus/doctor/check/rule_ambiguity.rb +0 -49
- data/lib/textus/doctor/check/schema_parse_error.rb +0 -28
- data/lib/textus/doctor/check/schema_violations.rb +0 -28
- data/lib/textus/doctor/check/schemas.rb +0 -26
- data/lib/textus/doctor/check/sentinels.rb +0 -60
- data/lib/textus/doctor/check/stale_reviewed_stamp.rb +0 -54
- data/lib/textus/doctor/check/templates.rb +0 -29
- data/lib/textus/doctor/check/unowned_schema_fields.rb +0 -40
- data/lib/textus/doctor/check.rb +0 -41
- data/lib/textus/doctor/validator.rb +0 -93
- data/lib/textus/doctor.rb +0 -54
- data/lib/textus/format/base.rb +0 -49
- data/lib/textus/format/json.rb +0 -102
- data/lib/textus/format/markdown.rb +0 -80
- data/lib/textus/format/shared.rb +0 -17
- data/lib/textus/format/text.rb +0 -55
- data/lib/textus/format/yaml.rb +0 -125
- data/lib/textus/format.rb +0 -46
- data/lib/textus/gate/auth.rb +0 -212
- data/lib/textus/gate.rb +0 -92
- data/lib/textus/init.rb +0 -157
- data/lib/textus/jobs.rb +0 -9
- data/lib/textus/key/distance.rb +0 -55
- data/lib/textus/key/grammar.rb +0 -33
- data/lib/textus/key/matching.rb +0 -24
- data/lib/textus/key/path.rb +0 -28
- data/lib/textus/manifest/capabilities.rb +0 -29
- data/lib/textus/manifest/data.rb +0 -102
- data/lib/textus/manifest/entry/base.rb +0 -105
- data/lib/textus/manifest/entry/ignore_matcher.rb +0 -46
- data/lib/textus/manifest/entry/leaf.rb +0 -17
- data/lib/textus/manifest/entry/nested.rb +0 -37
- data/lib/textus/manifest/entry/parser.rb +0 -99
- data/lib/textus/manifest/entry/produced.rb +0 -36
- data/lib/textus/manifest/entry/publish/mode.rb +0 -45
- data/lib/textus/manifest/entry/publish/none.rb +0 -14
- data/lib/textus/manifest/entry/publish/subtree_mirror.rb +0 -73
- data/lib/textus/manifest/entry/publish/template.rb +0 -16
- data/lib/textus/manifest/entry/publish/to_paths.rb +0 -76
- data/lib/textus/manifest/entry/publish/tree.rb +0 -50
- data/lib/textus/manifest/entry/publish.rb +0 -45
- data/lib/textus/manifest/entry/validators/format_matrix.rb +0 -21
- data/lib/textus/manifest/entry/validators/ignore.rb +0 -28
- data/lib/textus/manifest/entry/validators/publish.rb +0 -30
- data/lib/textus/manifest/entry/validators.rb +0 -18
- data/lib/textus/manifest/entry.rb +0 -8
- data/lib/textus/manifest/policy/matcher.rb +0 -51
- data/lib/textus/manifest/policy/publish_target.rb +0 -34
- data/lib/textus/manifest/policy/react.rb +0 -30
- data/lib/textus/manifest/policy/retention.rb +0 -29
- data/lib/textus/manifest/policy/source.rb +0 -30
- data/lib/textus/manifest/policy.rb +0 -95
- data/lib/textus/manifest/resolver.rb +0 -118
- data/lib/textus/manifest/rules.rb +0 -86
- data/lib/textus/manifest/schema/contract.rb +0 -61
- data/lib/textus/manifest/schema/keys.rb +0 -90
- data/lib/textus/manifest/schema/semantics/cross_field.rb +0 -53
- data/lib/textus/manifest/schema/semantics/invariants.rb +0 -125
- data/lib/textus/manifest/schema/semantics/migration.rb +0 -83
- data/lib/textus/manifest/schema/semantics.rb +0 -27
- data/lib/textus/manifest/schema/validator.rb +0 -48
- data/lib/textus/manifest/schema/vocabulary.rb +0 -25
- data/lib/textus/manifest/schema.rb +0 -30
- data/lib/textus/manifest.rb +0 -65
- data/lib/textus/meta.rb +0 -54
- data/lib/textus/port/audit_log.rb +0 -268
- data/lib/textus/port/build_lock.rb +0 -64
- data/lib/textus/port/clock.rb +0 -10
- data/lib/textus/port/publisher.rb +0 -60
- data/lib/textus/port/sentinel_store.rb +0 -83
- data/lib/textus/port/storage/file_stat.rb +0 -19
- data/lib/textus/port/storage/file_store.rb +0 -26
- data/lib/textus/port/store.rb +0 -93
- data/lib/textus/port/watcher_lock.rb +0 -48
- data/lib/textus/produce/engine.rb +0 -50
- data/lib/textus/produce/render.rb +0 -20
- data/lib/textus/schema/tools.rb +0 -115
- data/lib/textus/schema.rb +0 -100
- data/lib/textus/schemas.rb +0 -54
- data/lib/textus/store/compositor.rb +0 -34
- data/lib/textus/store/container.rb +0 -43
- data/lib/textus/store/cursor.rb +0 -26
- data/lib/textus/store/envelope/reader.rb +0 -43
- data/lib/textus/store/envelope/writer.rb +0 -195
- data/lib/textus/store/index/builder.rb +0 -74
- data/lib/textus/store/index/lookup.rb +0 -60
- data/lib/textus/store/jobs/base.rb +0 -13
- data/lib/textus/store/jobs/index.rb +0 -15
- data/lib/textus/store/jobs/materialize.rb +0 -15
- data/lib/textus/store/jobs/plan.rb +0 -11
- data/lib/textus/store/jobs/planner.rb +0 -104
- data/lib/textus/store/jobs/queue.rb +0 -154
- data/lib/textus/store/jobs/registry.rb +0 -19
- data/lib/textus/store/jobs/retention.rb +0 -50
- data/lib/textus/store/jobs/sweep.rb +0 -21
- data/lib/textus/store/jobs/worker.rb +0 -64
- data/lib/textus/store/session.rb +0 -37
- data/lib/textus/store.rb +0 -99
- data/lib/textus/surface/cli/verb/doctor.rb +0 -19
- data/lib/textus/surface/cli/verb/schema_diff.rb +0 -17
- data/lib/textus/surface/cli/verb/schema_init.rb +0 -21
- data/lib/textus/surface/cli/verb/schema_migrate.rb +0 -21
- data/lib/textus/surface/mcp.rb +0 -8
- data/lib/textus/surface/projector.rb +0 -27
- data/lib/textus/surface/role_scope.rb +0 -34
- data/lib/textus/value/command.rb +0 -16
- data/lib/textus/value/envelope.rb +0 -89
- data/lib/textus/value/result.rb +0 -26
- data/lib/textus/workflow/collector.rb +0 -27
- data/lib/textus/workflow/context.rb +0 -5
- data/lib/textus/workflow/loader.rb +0 -17
- data/lib/textus/workflow/pattern.rb +0 -18
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Lanes
|
|
3
|
+
module Ingest
|
|
4
|
+
module Handlers
|
|
5
|
+
SOURCE_KINDS = %w[url file asset].freeze
|
|
6
|
+
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def ingest(kind:, slug:, ctx:, call:, url: nil, path: nil, lane: nil, label: nil)
|
|
10
|
+
raise Textus::UsageError.new("kind must be one of #{SOURCE_KINDS.join("|")}") unless SOURCE_KINDS.include?(kind)
|
|
11
|
+
|
|
12
|
+
case kind
|
|
13
|
+
when "url" then raise Textus::UsageError.new("url required") unless url
|
|
14
|
+
when "file" then raise Textus::UsageError.new("path required") unless path
|
|
15
|
+
when "asset"
|
|
16
|
+
raise Textus::UsageError.new("path required") unless path
|
|
17
|
+
raise Textus::UsageError.new("lane required") unless lane
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
now = Time.now.utc
|
|
21
|
+
key = KeyBuilder.derive_key(now, kind, slug)
|
|
22
|
+
content_hash = KeyBuilder.compute_hash(kind:, url:, path:)
|
|
23
|
+
|
|
24
|
+
entry_type = EntryTypes.for(kind)
|
|
25
|
+
time = now
|
|
26
|
+
structured = entry_type.build(url:, path:, lane:, label:, time:, layout: ctx.layout)
|
|
27
|
+
|
|
28
|
+
lookup = Textus::Infra::Store::Index::Lookup.new(ctx.container.store.db)
|
|
29
|
+
duplicate_key = Dedup.find(lookup, content_hash, url:)
|
|
30
|
+
|
|
31
|
+
if duplicate_key && duplicate_key != key
|
|
32
|
+
old_content = ctx.read(key: duplicate_key)&.content || {}
|
|
33
|
+
Resolver.supersede(duplicate_key, key, ctx:, call:)
|
|
34
|
+
|
|
35
|
+
EntryTypes::Asset.move(old_content["asset"], lane, ctx.layout) if (kind == "asset") && old_content["asset"]
|
|
36
|
+
|
|
37
|
+
old_count = old_content["ingest_count"] || 1
|
|
38
|
+
structured["ingest_count"] = old_count + 1
|
|
39
|
+
structured["duplicated_at"] = now.utc.iso8601
|
|
40
|
+
structured["supersedes"] = duplicate_key
|
|
41
|
+
else
|
|
42
|
+
structured["ingest_count"] = 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
env = ctx.put(key:, meta: {}, body: nil, content: structured, call:)
|
|
46
|
+
|
|
47
|
+
IndexRebuilder.rebuild!(ctx.container.store.db, ctx.manifest.resolver)
|
|
48
|
+
|
|
49
|
+
{ "key" => key, "uid" => env.uid, "etag" => env.etag }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Lanes
|
|
3
|
+
module Ingest
|
|
4
|
+
module KeyBuilder
|
|
5
|
+
CONTENT_HASH_ALGO = "sha256"
|
|
6
|
+
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def derive_key(now, kind, slug)
|
|
10
|
+
date = now.strftime("%Y.%m.%d")
|
|
11
|
+
"raw.#{date}.#{kind}-#{slug}"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def compute_hash(kind:, url: nil, path: nil)
|
|
15
|
+
digest = Digest::SHA256.new
|
|
16
|
+
case kind
|
|
17
|
+
when "url"
|
|
18
|
+
raise ArgumentError.new("url required") unless url
|
|
19
|
+
|
|
20
|
+
digest.update(url)
|
|
21
|
+
when "file", "asset"
|
|
22
|
+
raise ArgumentError.new("path required") unless path
|
|
23
|
+
|
|
24
|
+
digest.file(path)
|
|
25
|
+
else
|
|
26
|
+
raise ArgumentError.new("unknown kind: #{kind}")
|
|
27
|
+
end
|
|
28
|
+
"#{CONTENT_HASH_ALGO}:#{digest.hexdigest}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Lanes
|
|
3
|
+
module Ingest
|
|
4
|
+
module Resolver
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def supersede(old_key, new_key, ctx:, call:)
|
|
8
|
+
old_env = ctx.read(key: old_key)
|
|
9
|
+
old_content = old_env&.content || {}
|
|
10
|
+
tombstone = {}
|
|
11
|
+
%w[ingested_at ingest_count].each { |k| tombstone[k] = old_content[k] if old_content.key?(k) }
|
|
12
|
+
source_kind = old_content.dig("source", "kind")
|
|
13
|
+
tombstone["source"] = { "kind" => source_kind } if source_kind
|
|
14
|
+
tombstone["superseded_by"] = new_key
|
|
15
|
+
|
|
16
|
+
ctx.put(key: old_key, meta: {}, body: nil, content: tombstone, call:)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Lanes
|
|
3
|
+
module Knowledge
|
|
4
|
+
module Freeform
|
|
5
|
+
def self.entries(manifest)
|
|
6
|
+
manifest.data.entries.reject { |e| e.key.start_with?("knowledge.loop.") }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.get(store, key)
|
|
10
|
+
store.get(key:)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Lanes
|
|
3
|
+
module Knowledge
|
|
4
|
+
module Handlers
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
LOOP_NODE_KEYS = {
|
|
8
|
+
"intent" => "knowledge.loop.intent",
|
|
9
|
+
"constraint.repo" => "knowledge.loop.constraint.repo",
|
|
10
|
+
"constraint.protocol" => "knowledge.loop.constraint.protocol",
|
|
11
|
+
"judgment.engineering" => "knowledge.loop.judgment.engineering",
|
|
12
|
+
"judgment.agent-behavior" => "knowledge.loop.judgment.agent-behavior",
|
|
13
|
+
"evidence" => "knowledge.loop.evidence",
|
|
14
|
+
"execution" => "knowledge.loop.execution.runbook",
|
|
15
|
+
"adoption" => "knowledge.loop.adoption",
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
def loop(ctx:, call:)
|
|
19
|
+
nodes = LOOP_NODE_KEYS.map { |name, key| { "name" => name, "key" => key } }
|
|
20
|
+
role = call&.role || "agent"
|
|
21
|
+
runner = Runner.new(ctx.manifest, cursor_store: ctx.cursor_store)
|
|
22
|
+
feedback = recent_feedback(ctx)
|
|
23
|
+
{
|
|
24
|
+
"loop" => nodes,
|
|
25
|
+
"count" => nodes.size,
|
|
26
|
+
"flow" => runner.flow,
|
|
27
|
+
"current_node" => runner.current_position(role),
|
|
28
|
+
"feedback" => feedback,
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def recent_feedback(ctx)
|
|
33
|
+
rows = ctx.entry_index&.search(schema: "loop-feedback", lane: "knowledge", query: nil, prefix: nil)
|
|
34
|
+
return [] unless rows&.any?
|
|
35
|
+
|
|
36
|
+
rows.map do |row|
|
|
37
|
+
env = ctx.read(key: row["key"])
|
|
38
|
+
meta = env&.meta || {}
|
|
39
|
+
{
|
|
40
|
+
"key" => row["key"],
|
|
41
|
+
"node" => meta["node"],
|
|
42
|
+
"confidence" => meta["confidence"],
|
|
43
|
+
"signal" => meta["signal"],
|
|
44
|
+
"summary" => env&.body || meta["summary"],
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
rescue Textus::Error
|
|
48
|
+
[]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Lanes
|
|
3
|
+
module Knowledge
|
|
4
|
+
class Runner
|
|
5
|
+
def initialize(manifest, cursor_store: nil)
|
|
6
|
+
@manifest = manifest
|
|
7
|
+
@cursor_store = cursor_store
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def loop_nodes
|
|
11
|
+
@manifest.data.entries.select do |e|
|
|
12
|
+
e.key.start_with?("knowledge.loop.") && e.key.count(".") == 2
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def flow
|
|
17
|
+
loop_nodes.map { |e| e.key.split(".").last }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def current_node(active_index: 0)
|
|
21
|
+
nodes = loop_nodes
|
|
22
|
+
return nil if nodes.empty?
|
|
23
|
+
|
|
24
|
+
idx = active_index % nodes.size
|
|
25
|
+
{ name: nodes[idx].key.split(".").last, index: idx, total: nodes.size }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def advance(active_index: 0)
|
|
29
|
+
current_node(active_index: active_index + 1).merge(advanced: true)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def current_position(role)
|
|
33
|
+
idx = @cursor_store ? @cursor_store.get_loop(role)[:index] : 0
|
|
34
|
+
current_node(active_index: idx)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def advance_role(role)
|
|
38
|
+
idx = @cursor_store ? @cursor_store.get_loop(role)[:index] + 1 : 1
|
|
39
|
+
@cursor_store&.set_loop(role, idx)
|
|
40
|
+
current_node(active_index: idx).merge(advanced: true)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Lanes
|
|
3
|
+
module Knowledge
|
|
4
|
+
module Verbs
|
|
5
|
+
end
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
Textus::Protocol::VerbRegistry.register(
|
|
11
|
+
Textus::Protocol::VerbRegistry::VerbSpec.new(
|
|
12
|
+
verb: :loop, summary: "Return the knowledge loop structure — node names and flow order.",
|
|
13
|
+
args: [], surfaces: %i[cli mcp], category: :read,
|
|
14
|
+
lane: :knowledge
|
|
15
|
+
),
|
|
16
|
+
)
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Lanes
|
|
3
|
+
module Scratchpad
|
|
4
|
+
module Handlers
|
|
5
|
+
PROPOSALS_PARENT_KEY = "scratchpad.proposals"
|
|
6
|
+
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
SESSION_PARENT_KEY = "scratchpad.sessions"
|
|
10
|
+
|
|
11
|
+
def session_open(ctx:, call:)
|
|
12
|
+
uuid = SecureRandom.uuid
|
|
13
|
+
key = "scratchpad.sessions.#{uuid}.session"
|
|
14
|
+
|
|
15
|
+
content = { "session_id" => uuid, "created_at" => Time.now.utc.iso8601, "status" => "active" }
|
|
16
|
+
ctx.put(key:, meta: {}, body: JSON.generate(content), content: nil, call:)
|
|
17
|
+
|
|
18
|
+
boot = begin
|
|
19
|
+
Textus::Protocol::Boot.build(container: ctx.container)
|
|
20
|
+
rescue StandardError
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
result = { "session_id" => uuid, "status" => "active" }
|
|
24
|
+
result["boot"] = boot if boot
|
|
25
|
+
result
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def parent_mentry(ctx)
|
|
29
|
+
ctx.manifest.data.entries.find { |e| e.key == SESSION_PARENT_KEY }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def propose(key:, ctx:, call:, meta: nil, data: nil)
|
|
33
|
+
body, content = data.is_a?(Hash) ? [nil, data] : [data, nil]
|
|
34
|
+
|
|
35
|
+
target_key = meta&.dig("proposal", "target_key")
|
|
36
|
+
if target_key
|
|
37
|
+
resolution = ctx.manifest.resolver.resolve(target_key)
|
|
38
|
+
Textus::Protocol::EntryConstraint.validate_naming!(target_key, resolution.entry)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
full_key = "#{PROPOSALS_PARENT_KEY}.#{key}"
|
|
42
|
+
env = ctx.put(key: full_key, meta: meta || {}, body:, content:, call:)
|
|
43
|
+
env.to_h_for_wire
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def accept(pending_key:, ctx:, call:, dry_run: false)
|
|
47
|
+
env = ctx.read(key: pending_key)
|
|
48
|
+
proposal = env&.meta&.dig("proposal")
|
|
49
|
+
raise Textus::ProposalError.new("entry has no proposal block: #{pending_key}") unless proposal
|
|
50
|
+
|
|
51
|
+
target = proposal["target_key"]
|
|
52
|
+
raise Textus::ProposalError.new("proposal missing target_key") unless target
|
|
53
|
+
|
|
54
|
+
action = proposal["action"] || "put"
|
|
55
|
+
|
|
56
|
+
if dry_run
|
|
57
|
+
target_env = ctx.read(key: target)
|
|
58
|
+
body_diff = Textus::Protocol::Diff.body(target_env&.body, env.body)
|
|
59
|
+
meta_diff = Textus::Protocol::Diff.meta(target_env&.meta&.dig("_meta") || {}, env.meta&.dig("_meta") || {})
|
|
60
|
+
result = { "dry_run" => true, "pending_key" => pending_key, "target_key" => target, "action" => action }
|
|
61
|
+
result["body"] = body_diff if body_diff
|
|
62
|
+
result["meta"] = meta_diff if meta_diff
|
|
63
|
+
result["summary"] = Textus::Protocol::Diff.summary(result)
|
|
64
|
+
return result
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
case action
|
|
68
|
+
when "put"
|
|
69
|
+
target_meta = env.meta.to_h.except("proposal")
|
|
70
|
+
|
|
71
|
+
# Parse body frontmatter to extract schema-relevant meta fields
|
|
72
|
+
resolution = ctx.manifest.resolver.resolve(target)
|
|
73
|
+
if env.body && !env.body.empty?
|
|
74
|
+
parsed = Textus::Protocol::Format.for(resolution.entry.format).parse(env.body)
|
|
75
|
+
body_meta = parsed["_meta"] || {}
|
|
76
|
+
target_meta = body_meta.merge(target_meta)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Re-validate constraints at promotion time
|
|
80
|
+
payload = Textus::Value::Payload.new(meta: target_meta, body: env.body, content: env.content)
|
|
81
|
+
Textus::Protocol::EntryConstraint.validate_naming!(target, resolution.entry)
|
|
82
|
+
Textus::Protocol::EntryConstraint.validate_schema!(payload, resolution.entry, schemas: ctx.schemas)
|
|
83
|
+
Textus::Protocol::EntryConstraint.validate_format!(payload, resolution.entry)
|
|
84
|
+
|
|
85
|
+
ctx.put(key: target, meta: target_meta, body: env.body, content: env.content, call:)
|
|
86
|
+
when "delete"
|
|
87
|
+
ctx.delete(key: target, call:)
|
|
88
|
+
else
|
|
89
|
+
raise Textus::ProposalError.new("unknown action: #{action}")
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
ctx.delete(key: pending_key, call:)
|
|
93
|
+
{ "protocol" => Textus::PROTOCOL, "accepted" => pending_key,
|
|
94
|
+
"target_key" => target, "action" => action, "cascade_key" => target }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def reject(pending_key:, ctx:, call:, reason: nil)
|
|
98
|
+
env = ctx.read(key: pending_key)
|
|
99
|
+
proposal = env&.meta&.dig("proposal")
|
|
100
|
+
raise Textus::ProposalError.new("entry has no proposal block: #{pending_key}") unless proposal
|
|
101
|
+
|
|
102
|
+
target_key = proposal["target_key"]
|
|
103
|
+
ctx.delete(key: pending_key, call:)
|
|
104
|
+
result = { "protocol" => Textus::PROTOCOL, "rejected" => pending_key, "target_key" => target_key }
|
|
105
|
+
result["reason"] = reason if reason
|
|
106
|
+
result
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def diff(pending_key:, ctx:, call:)
|
|
110
|
+
env = ctx.read(key: pending_key)
|
|
111
|
+
proposal = env&.meta&.dig("proposal")
|
|
112
|
+
raise Textus::ProposalError.new("entry has no proposal block: #{pending_key}") unless proposal
|
|
113
|
+
|
|
114
|
+
target_key = proposal["target_key"]
|
|
115
|
+
raise Textus::ProposalError.new("proposal missing target_key") unless target_key
|
|
116
|
+
|
|
117
|
+
target_env = ctx.read(key: target_key)
|
|
118
|
+
|
|
119
|
+
body_diff = Textus::Protocol::Diff.body(target_env&.body, env.body)
|
|
120
|
+
meta_diff = Textus::Protocol::Diff.meta(target_env&.meta&.dig("_meta") || {}, env.meta&.dig("_meta") || {})
|
|
121
|
+
|
|
122
|
+
entry = ctx.manifest.data.entries.find { |e| e.key == target_key }
|
|
123
|
+
target_schema = entry&.schema_ref
|
|
124
|
+
proposal_schema = env&.meta&.dig("_meta", "schema")
|
|
125
|
+
schema_diff = nil
|
|
126
|
+
if proposal_schema && target_schema != proposal_schema
|
|
127
|
+
schema_diff = Textus::Protocol::Diff.schema({ "schema" => target_schema }, { "schema" => proposal_schema })
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
result = { "pending_key" => pending_key, "target_key" => target_key }
|
|
131
|
+
result["body"] = body_diff if body_diff
|
|
132
|
+
result["meta"] = meta_diff if meta_diff
|
|
133
|
+
result["schema"] = schema_diff if schema_diff
|
|
134
|
+
result["summary"] = Textus::Protocol::Diff.summary(result)
|
|
135
|
+
result
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def session_close(session_id:, ctx:, call:, nodes_checked: nil)
|
|
139
|
+
key = "scratchpad.sessions.#{session_id}.session"
|
|
140
|
+
env = ctx.read(key:)
|
|
141
|
+
raise Textus::UsageError.new("session not found: #{session_id}") unless env
|
|
142
|
+
|
|
143
|
+
current = JSON.parse(env.body || "{}")
|
|
144
|
+
content = current.merge("status" => "sealed", "closed_at" => Time.now.utc.iso8601)
|
|
145
|
+
ctx.put(key:, meta: {}, body: JSON.generate(content), content: nil, call:)
|
|
146
|
+
|
|
147
|
+
feedback_key = "scratchpad.sessions.#{session_id}.feedback"
|
|
148
|
+
duration = current["created_at"] ? "#{((Time.now.utc - Time.parse(current["created_at"])) / 60).round}m" : "unknown"
|
|
149
|
+
feedback = {
|
|
150
|
+
"node" => "feedback",
|
|
151
|
+
"confidence" => "medium",
|
|
152
|
+
"signal" => "session_closed",
|
|
153
|
+
"summary" => "Session #{session_id[0..8]} sealed after #{duration}. " \
|
|
154
|
+
"Created at #{current["created_at"]}, closed at #{Time.now.utc.iso8601}.",
|
|
155
|
+
}
|
|
156
|
+
feedback["nodes_checked"] = nodes_checked if nodes_checked
|
|
157
|
+
ctx.put(key: feedback_key, meta: {}, body: JSON.generate(feedback), content: nil, call:)
|
|
158
|
+
|
|
159
|
+
{ "session_id" => session_id, "status" => "sealed", "feedback_key" => feedback_key }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Lanes
|
|
3
|
+
module Scratchpad
|
|
4
|
+
module Verbs
|
|
5
|
+
end
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
Textus::Protocol::VerbRegistry.register(
|
|
11
|
+
Textus::Protocol::VerbRegistry::VerbSpec.new(
|
|
12
|
+
verb: :session_open, summary: "Create a new agent session with workspace directories.",
|
|
13
|
+
args: [], surfaces: %i[cli mcp], category: :write,
|
|
14
|
+
lane: :scratchpad
|
|
15
|
+
),
|
|
16
|
+
)
|
|
17
|
+
Textus::Protocol::VerbRegistry.register(
|
|
18
|
+
Textus::Protocol::VerbRegistry::VerbSpec.new(
|
|
19
|
+
verb: :session_close, summary: "Seal an active session by ID.",
|
|
20
|
+
args: [Textus::Protocol::VerbRegistry::ArgSpec.arg(name: :session_id, required: true,
|
|
21
|
+
description: "UUID of the session to close"),
|
|
22
|
+
Textus::Protocol::VerbRegistry::ArgSpec.arg(name: :nodes_checked, type: Array,
|
|
23
|
+
description: "list of loop nodes checked during the session")],
|
|
24
|
+
surfaces: %i[cli mcp], category: :write,
|
|
25
|
+
lane: :scratchpad
|
|
26
|
+
),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
Textus::Protocol::VerbRegistry.register(
|
|
30
|
+
Textus::Protocol::VerbRegistry::VerbSpec.new(
|
|
31
|
+
verb: :propose, summary: "Write a proposal to scratchpad.proposals for human review.",
|
|
32
|
+
args: [Textus::Protocol::VerbRegistry::ArgSpec.arg(name: :key, required: true, positional: true,
|
|
33
|
+
description: "key relative to propose_lane, e.g. 'decisions.feature-x'"),
|
|
34
|
+
Textus::Protocol::VerbRegistry::ArgSpec.arg(name: :meta, type: Hash, wire_name: :_meta,
|
|
35
|
+
description: "frontmatter. Include a 'proposal:' block naming the target_key"),
|
|
36
|
+
Textus::Protocol::VerbRegistry::ArgSpec.arg(name: :data,
|
|
37
|
+
description: "unified payload — String (body) or Hash (content)")],
|
|
38
|
+
surfaces: %i[cli mcp], category: :write,
|
|
39
|
+
lane: :scratchpad
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
Textus::Protocol::VerbRegistry.register(
|
|
43
|
+
Textus::Protocol::VerbRegistry::VerbSpec.new(
|
|
44
|
+
verb: :accept, summary: "Apply a scratchpad.proposals entry to its target zone; requires author.",
|
|
45
|
+
args: [Textus::Protocol::VerbRegistry::ArgSpec.arg(name: :pending_key, required: true, positional: true,
|
|
46
|
+
description: "the queued proposal's key"),
|
|
47
|
+
Textus::Protocol::VerbRegistry::ArgSpec.arg(name: :dry_run, type: :boolean, default: false,
|
|
48
|
+
description: "when true, compute diff without promoting")],
|
|
49
|
+
surfaces: %i[cli mcp], category: :write,
|
|
50
|
+
lane: :scratchpad
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
Textus::Protocol::VerbRegistry.register(
|
|
54
|
+
Textus::Protocol::VerbRegistry::VerbSpec.new(
|
|
55
|
+
verb: :reject, summary: "Discard a scratchpad.proposals entry without applying it.",
|
|
56
|
+
args: [Textus::Protocol::VerbRegistry::ArgSpec.arg(name: :pending_key, required: true, positional: true,
|
|
57
|
+
description: "the queued proposal's key"),
|
|
58
|
+
Textus::Protocol::VerbRegistry::ArgSpec.arg(name: :reason,
|
|
59
|
+
description: "optional explanation for the rejection")],
|
|
60
|
+
surfaces: %i[cli mcp], category: :write,
|
|
61
|
+
lane: :scratchpad
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
Textus::Protocol::VerbRegistry.register(
|
|
65
|
+
Textus::Protocol::VerbRegistry::VerbSpec.new(
|
|
66
|
+
verb: :diff, summary: "Preview the diff between a scratchpad.proposals entry and its target.",
|
|
67
|
+
args: [Textus::Protocol::VerbRegistry::ArgSpec.arg(name: :pending_key, required: true, positional: true,
|
|
68
|
+
description: "the queued proposal's key")],
|
|
69
|
+
surfaces: %i[cli mcp],
|
|
70
|
+
category: :read,
|
|
71
|
+
lane: :scratchpad
|
|
72
|
+
),
|
|
73
|
+
)
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
module Textus
|
|
2
|
-
|
|
3
|
-
# Raised when a required arg is absent from the bound input. Surface
|
|
4
|
-
# adapters translate this to their native error (MCP ToolError, CLI
|
|
5
|
-
# UsageError); a direct Ruby call lets it surface as-is.
|
|
2
|
+
module Protocol
|
|
6
3
|
class MissingArgs < Textus::Error
|
|
7
4
|
attr_reader :spec, :missing
|
|
8
5
|
|
|
@@ -13,21 +10,16 @@ module Textus
|
|
|
13
10
|
end
|
|
14
11
|
end
|
|
15
12
|
|
|
16
|
-
# Validates and resolves a by-name inputs hash against a contract spec.
|
|
17
|
-
# Returns a flat hash with defaults and session_defaults filled in.
|
|
18
|
-
# Every caller receives the same shape — no positional/kwarg split.
|
|
19
13
|
module Binder
|
|
20
14
|
module_function
|
|
21
15
|
|
|
22
|
-
def bind(spec, inputs
|
|
16
|
+
def bind(spec, inputs)
|
|
23
17
|
missing = spec.required_args.reject { |a| inputs.key?(a.name) }
|
|
24
18
|
raise MissingArgs.new(spec, missing) unless missing.empty?
|
|
25
19
|
|
|
26
20
|
spec.args.each_with_object({}) do |a, h|
|
|
27
21
|
if inputs.key?(a.name)
|
|
28
22
|
h[a.name] = inputs[a.name]
|
|
29
|
-
elsif a.session_default && session
|
|
30
|
-
h[a.name] = session.public_send(a.session_default)
|
|
31
23
|
elsif !a.default.nil?
|
|
32
24
|
h[a.name] = a.default
|
|
33
25
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Textus
|
|
2
|
+
module Protocol
|
|
3
|
+
module Boot
|
|
4
|
+
module Catalog
|
|
5
|
+
CURATED_CLI_VERBS = [
|
|
6
|
+
{ "name" => "boot" },
|
|
7
|
+
{ "name" => "list" },
|
|
8
|
+
{ "name" => "loop" },
|
|
9
|
+
{ "name" => "get" },
|
|
10
|
+
{ "name" => "ingest" },
|
|
11
|
+
{ "name" => "where" },
|
|
12
|
+
{ "name" => "schema", "summary" => "schema operations: 'schema show KEY', 'schema diff', 'schema init', 'schema migrate'" },
|
|
13
|
+
{ "name" => "put" },
|
|
14
|
+
{ "name" => "propose" },
|
|
15
|
+
{ "name" => "accept" },
|
|
16
|
+
{ "name" => "key", "summary" => "key operations: 'key delete', 'key mv', 'key uid'" },
|
|
17
|
+
{ "name" => "drain" },
|
|
18
|
+
{ "name" => "session", "summary" => "session lifecycle: session_open, session_close" },
|
|
19
|
+
{ "name" => "audit" },
|
|
20
|
+
{ "name" => "blame" },
|
|
21
|
+
{ "name" => "pulse" },
|
|
22
|
+
].freeze
|
|
23
|
+
|
|
24
|
+
def self.build
|
|
25
|
+
summaries = Textus::Protocol::VerbRegistry.registered.to_h { |s| [s.verb.to_s, s.summary] }
|
|
26
|
+
CURATED_CLI_VERBS.map do |entry|
|
|
27
|
+
derived = summaries[entry["name"]]
|
|
28
|
+
derived ? entry.merge("summary" => derived) : entry
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|