kairos-chain 3.42.2 → 3.45.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/lib/kairos_mcp/version.rb +1 -1
  4. data/templates/skillsets/hestia/config/hestia.yml +24 -0
  5. data/templates/skillsets/hestia/lib/hestia/import_command_generator.rb +43 -0
  6. data/templates/skillsets/hestia/lib/hestia/place_router.rb +110 -1
  7. data/templates/skillsets/hestia/lib/hestia/public_presenter.rb +86 -0
  8. data/templates/skillsets/hestia/lib/hestia/public_rate_limiter.rb +47 -0
  9. data/templates/skillsets/hestia/lib/hestia/skill_auditor.rb +395 -0
  10. data/templates/skillsets/hestia/lib/hestia/skill_board.rb +8 -0
  11. data/templates/skillsets/hestia/lib/hestia/web/assets/marketplace.css +101 -0
  12. data/templates/skillsets/hestia/lib/hestia/web/assets/pico.min.css +4 -0
  13. data/templates/skillsets/hestia/lib/hestia/web/views/web_about.erb +49 -0
  14. data/templates/skillsets/hestia/lib/hestia/web/views/web_agent.erb +46 -0
  15. data/templates/skillsets/hestia/lib/hestia/web/views/web_anchor.erb +53 -0
  16. data/templates/skillsets/hestia/lib/hestia/web/views/web_index.erb +64 -0
  17. data/templates/skillsets/hestia/lib/hestia/web/views/web_layout.erb +33 -0
  18. data/templates/skillsets/hestia/lib/hestia/web/views/web_skill.erb +132 -0
  19. data/templates/skillsets/hestia/lib/hestia/web/views/web_verify.erb +31 -0
  20. data/templates/skillsets/hestia/lib/hestia/web_router.rb +339 -0
  21. data/templates/skillsets/hestia/lib/hestia.rb +5 -0
  22. data/templates/skillsets/hestia/skillset.json +1 -1
  23. data/templates/skillsets/synoptis/bin/khab_verify.rb +172 -0
  24. data/templates/skillsets/synoptis/bin/map_verify.rb +250 -0
  25. data/templates/skillsets/synoptis/lib/synoptis/anchoring/anchoring_rule.rb +184 -0
  26. data/templates/skillsets/synoptis/lib/synoptis/anchoring/attestation_types.rb +107 -0
  27. data/templates/skillsets/synoptis/lib/synoptis/anchoring/chain_credential.rb +187 -0
  28. data/templates/skillsets/synoptis/lib/synoptis/anchoring/containment.rb +222 -0
  29. data/templates/skillsets/synoptis/lib/synoptis/anchoring/conventions/khab-1.md +112 -0
  30. data/templates/skillsets/synoptis/lib/synoptis/anchoring/conventions/map-1.md +198 -0
  31. data/templates/skillsets/synoptis/lib/synoptis/anchoring/cumulative_commitment.rb +229 -0
  32. data/templates/skillsets/synoptis/lib/synoptis/anchoring/deposit_board.rb +288 -0
  33. data/templates/skillsets/synoptis/lib/synoptis/anchoring/entry.rb +215 -0
  34. data/templates/skillsets/synoptis/lib/synoptis/anchoring/head_binding.rb +294 -0
  35. data/templates/skillsets/synoptis/lib/synoptis/anchoring/log.rb +504 -0
  36. data/templates/skillsets/synoptis/lib/synoptis/anchoring/public_read.rb +57 -0
  37. data/templates/skillsets/synoptis/lib/synoptis/anchoring/public_verifier.rb +97 -0
  38. data/templates/skillsets/synoptis/lib/synoptis/anchoring/read_path.rb +70 -0
  39. data/templates/skillsets/synoptis/lib/synoptis/anchoring/succession.rb +222 -0
  40. data/templates/skillsets/synoptis/lib/synoptis/anchoring/write_budget.rb +121 -0
  41. data/templates/skillsets/synoptis/lib/synoptis/anchoring/write_path.rb +102 -0
  42. data/templates/skillsets/synoptis/lib/synoptis.rb +11 -0
  43. data/templates/skillsets/synoptis/test/test_head_anchor.rb +431 -0
  44. data/templates/skillsets/synoptis/test/test_map_anchoring.rb +359 -0
  45. metadata +36 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 45fd1079aa951da3ac40d0dcd9763e02c2a15413753d5452dab4fc07e928a3cb
4
- data.tar.gz: 93e3969307c08b26782c81804c9b5b10c475407e4770962a04ec29ab227a7951
3
+ metadata.gz: ea7401710d6467287e35146c84d731c08733f184fac36be13e76de11d7af5433
4
+ data.tar.gz: a823ba8bf1bb1157394ff04466cdcf96c0347eee50796816fe8c2fd1d188792c
5
5
  SHA512:
6
- metadata.gz: e0d95027c4457cb89a1dee063b3637dc568fce57735e2be211119ace92d684b42451b03b176887202bc2eb797d71cb49af9e3ca49f0e0e9e00689c88ff74c177
7
- data.tar.gz: fd1ac48f63e671ce7df974bab3efd35e2f3af8b626aeba655f56d3bffa933903d269f3d5775ae877203f9ef3050c66c5a3d5cbf69811ec113b9f5235b35d6574
6
+ metadata.gz: a11ba74c10eb1c56ef15b57992755083b6d36bd4fef1ea73bf1cae64afc1f07ad26f8774ee703c3c98eefa25c8a422441c5c5af032b9ae523d7518c7dc50193d
7
+ data.tar.gz: 26f95dd3245e9e39311349ef540954d200985a59bf9da38b3b08256d11ec50a445d60cc3f0c41538b6a0c0bfb2eb591ce742dc4523950d9ffe8f06471d22e50a
data/CHANGELOG.md CHANGED
@@ -4,6 +4,35 @@ All notable changes to the `kairos-chain` gem will be documented in this file.
4
4
 
5
5
  This project follows [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [3.45.0] - 2026-07-21
8
+
9
+ ### Synoptis — mutual anchoring protocol MAP-1..4 (map-1, AUD-L2)
10
+
11
+ Implements AUD-L2 design v0.5 (FROZEN) slice 1 in the synoptis SkillSet:
12
+
13
+ - `conventions/map-1.md`: content-addressed mutual-anchoring convention
14
+ (chain identity credential, typed attestation vocabulary, succession
15
+ records, declared anchoring rule, changeover event). Builds on khab-1
16
+ without modifying it.
17
+ - `chain_credential.rb`: Ed25519 self-authenticating chain identity
18
+ credential — verification needs no registry and no network.
19
+ - `succession.rb`: succession governance (earliest non-retracted designation
20
+ governs; at-or-before changeover boundary; issuer-only retraction;
21
+ retract-and-redesignate trails surfaced in the contested register).
22
+ - `anchoring_rule.rb`: result-free declared anchoring rule (closed schema)
23
+ with a coverage checker that reports and never enforces.
24
+ - `attestation_types.rb`: MAP-4 vocabulary intake validation and anchor-log
25
+ retraction coherence (issuer = depositor at map-1, disclosed deferral).
26
+ - `entry.rb` / `log.rb` / `write_path.rb`: additive `attestation_type` on
27
+ newly appended entries only (AHM-4 byte-invariance preserved).
28
+ - `bin/map_verify.rb`: standalone offline verifier (credential, attestation,
29
+ succession, coverage, pair, retraction subcommands).
30
+ - `test/test_map_anchoring.rb`: 75 design-constraint tests.
31
+
32
+ Design review converged R5 5/6 APPROVE; implementation review converged
33
+ R4 5/5 APPROVE with zero residual P0/P1. The inaugural mutual anchor with a
34
+ second, independently operated HestiaChain instance is deliberately pending.
35
+
7
36
  ## [3.42.2] - 2026-07-14
8
37
 
9
38
  ### Knowledge — loop_engineering_patterns §B: Model/Effort budget allocation
@@ -1,4 +1,4 @@
1
1
  module KairosMcp
2
- VERSION = "3.42.2"
2
+ VERSION = "3.45.0"
3
3
  CHANGELOG_URL = "https://github.com/masaomi/KairosChain_2026/blob/main/CHANGELOG.md"
4
4
  end
@@ -13,6 +13,30 @@ meeting_place:
13
13
  # registry_path: storage/agent_registry.json
14
14
  # parent_place_url: null # URL of parent place for federation
15
15
 
16
+ # Public web surface (unauthenticated, read-only): skill catalog, JSON API,
17
+ # and — when anchoring is enabled — the ANC-7 verification view.
18
+ # Mounted by PlaceRouter under /place/web/* and /place/api/v1/*.
19
+ web_ui:
20
+ enabled: true
21
+ # place_host / place_url anchor the public links and the ANC-7 verification
22
+ # address (place://<place_host>/anchor/<slug>). Set to the served hostname.
23
+ # place_host: meeting.genomicschain.io
24
+ # place_url: https://meeting.genomicschain.io
25
+
26
+ # Anchor attestation capability (ANC-1..9 / BRD-1..4). Opt-in: when disabled
27
+ # the catalog still serves but /place/web/verify and /place/web/anchor/* 404.
28
+ # Enable on the place that hosts external-reference (content-by-reference)
29
+ # anchors — e.g. the constitutive-note / GenomicsChain provenance deployment.
30
+ anchoring:
31
+ enabled: false
32
+ # ANC-9 free-write budget (mechanism, design §11) — tune to observed load.
33
+ # The Sybil disclosure is shown regardless of the numbers.
34
+ per_identity: 60 # writes per identity per window
35
+ aggregate: 600 # writes across all identities per window
36
+ window_seconds: 3600 # refill window
37
+ # log_path: storage/hestia_anchor_log.json
38
+ # attestation_store_path: storage/hestia_anchor_attestations.json
39
+
16
40
  # Deposit policy (operator-configurable)
17
41
  deposit_policy:
18
42
  max_skill_size_bytes: 100000
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+
5
+ module Hestia
6
+ # Generates shell-safe import commands for various LLM tools.
7
+ # All interpolated values are escaped via Shellwords.shellescape.
8
+ class ImportCommandGenerator
9
+ def initialize(place_url:)
10
+ @place_url = place_url
11
+ end
12
+
13
+ def commands_for(skill, deposit_id:)
14
+ safe_url = Shellwords.shellescape(@place_url)
15
+ safe_skill_id = Shellwords.shellescape(skill[:skill_id] || deposit_id.split('__', 2).last)
16
+
17
+ {
18
+ claude_code: claude_code_command(safe_skill_id, safe_url),
19
+ codex: codex_command(safe_skill_id, safe_url),
20
+ kairos_cli: kairos_cli_command(safe_skill_id, safe_url),
21
+ curl_preview: curl_preview_command(Shellwords.shellescape(deposit_id), safe_url)
22
+ }
23
+ end
24
+
25
+ private
26
+
27
+ def claude_code_command(skill_id, place_url)
28
+ "claude -p \"Use meeting_acquire_skill to acquire #{skill_id} from place #{place_url}\""
29
+ end
30
+
31
+ def codex_command(skill_id, place_url)
32
+ "echo \"Use meeting_acquire_skill to acquire #{skill_id} from place #{place_url}\" | codex exec -"
33
+ end
34
+
35
+ def kairos_cli_command(skill_id, place_url)
36
+ "kairos-chain acquire --place #{place_url} --skill #{skill_id}"
37
+ end
38
+
39
+ def curl_preview_command(deposit_id, place_url)
40
+ "curl -s #{place_url}/place/api/v1/skill/#{deposit_id}"
41
+ end
42
+ end
43
+ end
@@ -55,7 +55,8 @@ module Hestia
55
55
  end
56
56
  end
57
57
 
58
- attr_reader :registry, :skill_board, :heartbeat_manager, :session_store, :started_at, :extensions
58
+ attr_reader :registry, :skill_board, :heartbeat_manager, :session_store, :started_at, :extensions,
59
+ :web_router, :anchor_log, :anchor_board
59
60
 
60
61
  def initialize(config: nil)
61
62
  @config = config || ::Hestia.load_config
@@ -63,6 +64,16 @@ module Hestia
63
64
  @skill_board = nil
64
65
  @heartbeat_manager = nil
65
66
  @session_store = nil
67
+ # The public (unauthenticated) web UI + JSON API + ANC-7 verification view.
68
+ # Constructed in #start when the operator enables it (default on). The
69
+ # Meeting Place SkillSet owns its own public surface here rather than in the
70
+ # core HTTP server, so mounting it never crosses the L0/SkillSet boundary.
71
+ @web_router = nil
72
+ # Anchor store instances (nil until #start with anchoring enabled). Exposed
73
+ # so an authenticated write entry point can deposit into the same log/board
74
+ # the public ANC-7 view reads from.
75
+ @anchor_log = nil
76
+ @anchor_board = nil
66
77
  @started = false
67
78
  @started_at = nil
68
79
  @self_id = nil
@@ -121,6 +132,16 @@ module Hestia
121
132
  observer_id: @self_id
122
133
  )
123
134
 
135
+ # Anchor store (ANC-1 log + BRD board + ANC-9 budget). Built independently
136
+ # of the web UI so it also backs the authenticated write path (the
137
+ # inaugural / external anchoring deposit), and exposed via attr_reader so an
138
+ # operator write entry point deposits into the same instances the public
139
+ # view reads from. [nil, nil] when anchoring is disabled.
140
+ @anchor_log, @anchor_board = build_anchor_store(place_config, default_storage)
141
+
142
+ # Mount the public web surface (catalog/api + ANC-7 verification view).
143
+ @web_router = build_web_router(place_config)
144
+
124
145
  # Self-register: the Place IS also a participant (主客未分)
125
146
  @registry.self_register(identity)
126
147
 
@@ -135,6 +156,84 @@ module Hestia
135
156
  }
136
157
  end
137
158
 
159
+ # Build the public WebRouter (catalog/api + ANC-7 verification view).
160
+ #
161
+ # The web classes require rack/utils (present in the server bundle) and are
162
+ # required lazily here so a place that never serves the web UI does not pull
163
+ # in rack at load time. A missing dependency degrades to "web UI not mounted"
164
+ # (the authenticated API keeps working) rather than a boot failure.
165
+ #
166
+ # @return [WebRouter, nil] nil when disabled or the dependency is unavailable.
167
+ def build_web_router(place_config)
168
+ web_config = place_config['web_ui'] || {}
169
+ return nil unless web_config.fetch('enabled', true)
170
+
171
+ require_relative 'public_rate_limiter'
172
+ require_relative 'public_presenter'
173
+ require_relative 'import_command_generator'
174
+ require_relative 'web_router'
175
+
176
+ place_host = web_config['place_host']
177
+ place_url = web_config['place_url'] || (place_host && "https://#{place_host}")
178
+
179
+ WebRouter.new(
180
+ skill_board: @skill_board,
181
+ agent_registry: @registry,
182
+ config: {
183
+ 'name' => place_config['name'] || 'KairosChain Meeting Place',
184
+ 'place_host' => place_host,
185
+ 'place_url' => place_url
186
+ }.compact,
187
+ anchor_read: build_anchor_read
188
+ )
189
+ rescue LoadError => e
190
+ warn "[PlaceRouter] Public web UI not mounted (missing dependency): #{e.message}"
191
+ nil
192
+ end
193
+
194
+ # Build the Synoptis-owned public read handle the WebRouter presents through
195
+ # (AHM-1). nil when anchoring is disabled (@anchor_log unset), in which case
196
+ # the anchor routes 404. The write-side handles (@anchor_log/@anchor_board)
197
+ # stay owned here for the authenticated deposit path (design §4.3).
198
+ def build_anchor_read
199
+ return nil unless @anchor_log
200
+
201
+ Synoptis::Anchoring::PublicRead.new(log: @anchor_log, board: @anchor_board)
202
+ end
203
+
204
+ # Build the anchor store (ANC-1 log + BRD deposit board + ANC-9 write budget)
205
+ # when the operator enables anchoring. Returns [nil, nil] when disabled, in
206
+ # which case the WebRouter serves the catalog but the /place/web/verify and
207
+ # /place/web/anchor/* routes 404 (the capability is simply not present).
208
+ #
209
+ # The operator identity (@self_id) is the ANC-5/ANC-8 control boundary: it is
210
+ # the only identity permitted to withdraw any entry and the one against which
211
+ # same-party vs foreign is derived.
212
+ def build_anchor_store(place_config, default_storage)
213
+ anchor_config = place_config['anchoring'] || {}
214
+ return [nil, nil] unless anchor_config['enabled']
215
+
216
+ require 'synoptis'
217
+
218
+ log = Synoptis::Anchoring::Log.new(
219
+ storage_path: anchor_config['log_path'] || "#{default_storage}hestia_anchor_log.json",
220
+ operator_id: @self_id
221
+ )
222
+ budget = Synoptis::Anchoring::WriteBudget.new(
223
+ per_identity: anchor_config['per_identity'] || Synoptis::Anchoring::WriteBudget::DEFAULT_PER_IDENTITY,
224
+ aggregate: anchor_config['aggregate'] || Synoptis::Anchoring::WriteBudget::DEFAULT_AGGREGATE,
225
+ window_seconds: anchor_config['window_seconds'] || Synoptis::Anchoring::WriteBudget::DEFAULT_WINDOW_SECONDS,
226
+ operator_id: @self_id
227
+ )
228
+ board = Synoptis::Anchoring::DepositBoard.new(
229
+ log: log,
230
+ attestation_store_path: anchor_config['attestation_store_path'] ||
231
+ "#{default_storage}hestia_anchor_attestations.json",
232
+ budget: budget
233
+ )
234
+ [log, board]
235
+ end
236
+
138
237
  # Rack-compatible call method.
139
238
  def call(env)
140
239
  unless @started
@@ -147,6 +246,16 @@ module Hestia
147
246
  request_method = env['REQUEST_METHOD']
148
247
  path = env['PATH_INFO']
149
248
 
249
+ # Public, unauthenticated, read-only web surface (ANC-7 verification view,
250
+ # skill catalog, JSON API). Delegated before the auth flow — same position
251
+ # as the other unauthenticated endpoints below. The WebRouter enforces
252
+ # GET-only, IP rate limiting, and HTML escaping internally.
253
+ if @web_router &&
254
+ (path == '/place/web' || path.start_with?('/place/web/') ||
255
+ path.start_with?('/place/api/v1/'))
256
+ return @web_router.call(env)
257
+ end
258
+
150
259
  # Unauthenticated endpoints
151
260
  if request_method == 'GET' && path == '/place/v1/info'
152
261
  return handle_info
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hestia
4
+ # Shared presenter for public data — used by both JSON API and HTML views.
5
+ # Truncates IDs and strips content to prevent information leakage.
6
+ class PublicPresenter
7
+ ID_TRUNCATE_LENGTH = 12
8
+
9
+ def catalog_entry(entry, auditor = nil)
10
+ deposit_id = make_deposit_id(entry)
11
+ {
12
+ deposit_id: deposit_id,
13
+ skill_id: entry[:skill_id],
14
+ name: entry[:name],
15
+ description: truncate(entry[:description], 500),
16
+ tags: entry[:tags] || [],
17
+ format: entry[:format],
18
+ type: entry[:type],
19
+ size_bytes: entry[:size_bytes],
20
+ content_hash: entry[:content_hash]&.slice(0, 16),
21
+ deposited_at: entry[:deposited_at],
22
+ depositor_id: truncate_id(entry[:agent_id] || entry[:depositor_id]),
23
+ summary: truncate(entry[:summary], 500),
24
+ version: entry[:version],
25
+ license: entry[:license],
26
+ attestation_count: entry[:attestations]&.size || 0,
27
+ audit_status: auditor ? auditor.audit_status(deposit_id)[:status] : nil
28
+ }
29
+ end
30
+
31
+ def skill_detail(skill, auditor = nil)
32
+ base = catalog_entry(skill, auditor)
33
+ base.merge(
34
+ sections: skill[:sections],
35
+ first_lines: skill[:first_lines],
36
+ input_output: skill[:input_output],
37
+ attestations: present_attestations(skill[:attestations]),
38
+ trust_notice: skill[:trust_notice],
39
+ audit_detail: auditor ? auditor.audit_status(make_deposit_id(skill)) : nil
40
+ )
41
+ end
42
+
43
+ def agent_profile(agent)
44
+ id = agent.respond_to?(:id) ? agent.id : agent[:id]
45
+ {
46
+ agent_id: truncate_id(id),
47
+ name: agent.respond_to?(:name) ? agent.name : agent[:name],
48
+ description: agent.respond_to?(:description) ? agent.description : agent[:description],
49
+ registered_at: agent.respond_to?(:registered_at) ? agent.registered_at : agent[:registered_at]
50
+ }
51
+ end
52
+
53
+ def make_deposit_id(entry)
54
+ agent = entry[:agent_id] || entry[:depositor_id]
55
+ skill = entry[:skill_id]
56
+ "#{agent}__#{skill}" if agent && skill
57
+ end
58
+
59
+ private
60
+
61
+ def present_attestations(attestations)
62
+ return [] unless attestations
63
+
64
+ attestations.map do |a|
65
+ {
66
+ claim: a[:claim],
67
+ attester_id: truncate_id(a[:attester_id]),
68
+ attester_name: a[:attester_name],
69
+ actor_role: a[:actor_role],
70
+ has_signature: a[:has_signature],
71
+ deposited_at: a[:deposited_at]
72
+ }
73
+ end
74
+ end
75
+
76
+ def truncate_id(id)
77
+ return nil unless id
78
+ id.length > ID_TRUNCATE_LENGTH ? "#{id[0, ID_TRUNCATE_LENGTH]}..." : id
79
+ end
80
+
81
+ def truncate(text, max)
82
+ return nil unless text
83
+ text.length > max ? "#{text[0, max]}..." : text
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hestia
4
+ # IP-based rate limiter for public web/API endpoints.
5
+ # Bounded memory: TTL eviction + max tracked IPs.
6
+ class PublicRateLimiter
7
+ MAX_REQUESTS_PER_MINUTE = 30
8
+ MAX_TRACKED_IPS = 10_000
9
+ CLEANUP_INTERVAL = 1000
10
+ STALE_SECONDS = 300
11
+
12
+ def initialize(max_rpm: MAX_REQUESTS_PER_MINUTE)
13
+ @max_rpm = max_rpm
14
+ @requests = {}
15
+ @mutex = Mutex.new
16
+ @total_requests = 0
17
+ end
18
+
19
+ def allow?(ip)
20
+ @mutex.synchronize do
21
+ @total_requests += 1
22
+ cleanup! if (@total_requests % CLEANUP_INTERVAL).zero?
23
+
24
+ now = Time.now.to_f
25
+ @requests[ip] = (@requests[ip] || []).select { |t| now - t < 60 }
26
+
27
+ return false if @requests[ip].size >= @max_rpm
28
+
29
+ @requests[ip] << now
30
+ true
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def cleanup!
37
+ now = Time.now.to_f
38
+ @requests.delete_if { |_ip, timestamps| timestamps.all? { |t| now - t >= STALE_SECONDS } }
39
+
40
+ return unless @requests.size > MAX_TRACKED_IPS
41
+
42
+ sorted = @requests.sort_by { |_ip, ts| ts.last || 0 }
43
+ excess = @requests.size - MAX_TRACKED_IPS
44
+ sorted.first(excess).each { |ip, _| @requests.delete(ip) }
45
+ end
46
+ end
47
+ end