kairos-chain 2.10.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/bin/kairos-chain +1 -1
  4. data/lib/kairos_mcp/auth/token_store.rb +6 -2
  5. data/lib/kairos_mcp/http_server.rb +10 -0
  6. data/lib/kairos_mcp/safety.rb +8 -0
  7. data/lib/kairos_mcp/tools/token_manage.rb +1 -0
  8. data/lib/kairos_mcp/version.rb +1 -1
  9. data/templates/knowledge/kairoschain_setup/kairoschain_setup.md +2 -0
  10. data/templates/knowledge/kairoschain_setup_jp/kairoschain_setup_jp.md +2 -0
  11. data/templates/knowledge/service_grant_access_control/service_grant_access_control.md +108 -0
  12. data/templates/knowledge/service_grant_access_control_jp/service_grant_access_control_jp.md +108 -0
  13. data/templates/skillsets/hestia/lib/hestia/place_router.rb +96 -3
  14. data/templates/skillsets/mmp/lib/mmp/meeting_session_store.rb +39 -3
  15. data/templates/skillsets/service_grant/config/service_grant.yml +118 -0
  16. data/templates/skillsets/service_grant/knowledge/service_grant_guide/service_grant_guide.md +106 -0
  17. data/templates/skillsets/service_grant/lib/service_grant/access_checker.rb +89 -0
  18. data/templates/skillsets/service_grant/lib/service_grant/access_gate.rb +65 -0
  19. data/templates/skillsets/service_grant/lib/service_grant/client_ip_resolver.rb +25 -0
  20. data/templates/skillsets/service_grant/lib/service_grant/cycle_manager.rb +41 -0
  21. data/templates/skillsets/service_grant/lib/service_grant/errors.rb +24 -0
  22. data/templates/skillsets/service_grant/lib/service_grant/grant_manager.rb +212 -0
  23. data/templates/skillsets/service_grant/lib/service_grant/ip_rate_tracker.rb +74 -0
  24. data/templates/skillsets/service_grant/lib/service_grant/payment_verifier.rb +301 -0
  25. data/templates/skillsets/service_grant/lib/service_grant/pg_circuit_breaker.rb +108 -0
  26. data/templates/skillsets/service_grant/lib/service_grant/pg_connection_pool.rb +119 -0
  27. data/templates/skillsets/service_grant/lib/service_grant/place_middleware.rb +55 -0
  28. data/templates/skillsets/service_grant/lib/service_grant/plan_registry.rb +127 -0
  29. data/templates/skillsets/service_grant/lib/service_grant/request_enricher.rb +26 -0
  30. data/templates/skillsets/service_grant/lib/service_grant/trust_scorer_adapter.rb +78 -0
  31. data/templates/skillsets/service_grant/lib/service_grant/usage_tracker.rb +118 -0
  32. data/templates/skillsets/service_grant/lib/service_grant.rb +248 -0
  33. data/templates/skillsets/service_grant/migrations/001_service_grant_schema.sql +92 -0
  34. data/templates/skillsets/service_grant/migrations/002_grant_ip_events.sql +11 -0
  35. data/templates/skillsets/service_grant/migrations/003_subscription_and_provider_tx.sql +20 -0
  36. data/templates/skillsets/service_grant/skillset.json +18 -0
  37. data/templates/skillsets/service_grant/test/test_service_grant.rb +2546 -0
  38. data/templates/skillsets/service_grant/tools/service_grant_manage.rb +139 -0
  39. data/templates/skillsets/service_grant/tools/service_grant_migrate.rb +150 -0
  40. data/templates/skillsets/service_grant/tools/service_grant_pay.rb +60 -0
  41. data/templates/skillsets/service_grant/tools/service_grant_status.rb +98 -0
  42. data/templates/skillsets/synoptis/lib/synoptis/trust_identity.rb +53 -0
  43. data/templates/skillsets/synoptis/lib/synoptis/trust_scorer.rb +207 -7
  44. metadata +40 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a238ca634bf5f063383755826516315843ec93b5bbf6a1d9740a76ef367e9450
4
- data.tar.gz: f9f3098d7c48248fae20ebaaba7d50353a70792e33dde79c512370017d78ef24
3
+ metadata.gz: b831eba22baf94d43c7592c5ff2dbf12ca928dcea034b713837bf5253e691f99
4
+ data.tar.gz: 65c6cd548075ccfccf85d3330548fe51280d39f7c767f6de96c8d64fa5afd799
5
5
  SHA512:
6
- metadata.gz: b6360bcfaaf7fd8b3920e5b99992cf515cf1448257a29128fc6e33804c872e73339a558829b0ab5891f4699f4117aea71f1fbcfe0ca39d76171de6397138aecb
7
- data.tar.gz: 7243622826d6e9b994f29ccb463a6f2b1af78e7e9f3605fc57e247eee1368bcb7ca929406cfa9443228ea3cad0d985f7e78f0def14388b5b03a947c4a1c4b6e7
6
+ metadata.gz: df294d0d060010e7d589e972ab12164c9cc1451bfd069693d5296bafb9512e95c985d83f4cb849a9d5064f76f0c89b57fbad2245d517f4cc759ce00ebae0f446
7
+ data.tar.gz: 81a7d541ebf5afc58b938f2b6d2b22c0f38c3c32d69705c2b6cd4074425292a0defa98a4adc74834659bef418a78539b2f7f2a90d48eb26df1f6961c1e41c978
data/CHANGELOG.md CHANGED
@@ -4,6 +4,44 @@ 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.0.0] - 2026-03-21
8
+
9
+ ### Added
10
+
11
+ - **Service Grant SkillSet** (`service_grant`): New SkillSet providing generic, service-independent access control and billing for any KairosChain-based service. Designed and implemented through multi-LLM review methodology (Claude Opus 4.6, GPT-5.4, Composer-2) across 6 phases.
12
+ - **Phase 0**: Core/Hestia prerequisites — PlaceRouter middleware hooks, session store pubkey_hash support, token store extension
13
+ - **Phase 1**: Basic access control MVP — GrantManager (auto-grant with ON CONFLICT), UsageTracker (atomic try_consume), AccessChecker (unified pipeline), AccessGate (Path A), PlaceMiddleware (Path B), PlanRegistry (YAML config), PgConnectionPool (thread-safe with circuit breaker), IpRateTracker (anti-Sybil), CycleManager, RequestEnricher, Safety policies, admin tools
14
+ - **Phase 2**: Synoptis Trust Score integration — TrustScorerAdapter (quality+bridge scoring with caching), anti-collusion PageRank (zero-weight cartel detection), TrustIdentity (canonical `agent://` URIs), PgCircuitBreaker hardening, PoolExhaustedError hierarchy
15
+ - **Phase 3a**: PaymentVerifier (proof-centric design) — cryptographic signature verification via Synoptis Verifier, issuer authorization, freshness/revocation checks, evidence validation (amount, currency, nonce), idempotent duplicate handling (PG::UniqueViolation rescue), atomic upgrade transaction
16
+ - **Phase 3b**: Subscription expiry + provider_tx_id — lazy downgrade with atomic conditional UPDATE, concurrent renewal re-read, subscription_duration config validation (1-3650 days), provider_tx_id tracking
17
+ - **GrantManager-PaymentVerifier unification**: Plan-change SQL consolidated into `apply_plan_upgrade` (single source), event recording unified via `record_plan_upgrade` (called after COMMIT)
18
+ - 4 billing models: `free`, `per_action`, `metered`, `subscription`
19
+ - 4 MCP tools: `service_grant_status`, `service_grant_manage`, `service_grant_migrate`, `service_grant_pay`
20
+ - 3 SQL migrations, YAML-driven plan configuration
21
+ - Anti-Sybil: IP rate limiting (5/hour, PG-backed), cooldown (5 min write delay), trust score gating
22
+ - 159 unit tests, 232 assertions
23
+ - Bundled L1 knowledge: `service_grant_guide`
24
+
25
+ - **Multi-LLM Design Review L1 Knowledge** (`multi_llm_design_review` v2.1): Methodology and CLI automation for parallel multi-LLM code review.
26
+ - Auto mode: Codex (GPT-5.4) + Cursor Agent (Composer-2) + Claude Code (Opus 4.6) in parallel
27
+ - Manual mode fallback with structured prompt generation
28
+ - Prompt Generation Rules: output filename specification, auto-execution commands
29
+ - Convergence rules: 2/3 APPROVE = proceed, any REJECT = revise
30
+ - Observed LLM role differentiation across structural, seam, and safety layers
31
+
32
+ - **SkillSet Implementation Quality Guide** (`skillset_implementation_quality_guide`): Design constraint tests and wiring checklist derived from Service Grant multi-LLM review experiment.
33
+
34
+ - **L1 Knowledge**: Service Grant access control documentation (EN/JP) with `readme_order: 4.9`
35
+
36
+ ### Changed
37
+
38
+ - **HestiaChain PlaceRouter**: Added `register_middleware`/`unregister_middleware` hooks, `ROUTE_ACTION_MAP`, middleware invocation in request handling, pubkey_hash resolution from session store
39
+ - **MMP MeetingSessionStore**: Added `pubkey_hash` storage and `pubkey_hash_for(peer_id)` method
40
+ - **Synoptis TrustScorer**: Extended with anti-collusion PageRank, external attestation weighting, bridge score calculation, attestation weight with zero-weight floor for cartels
41
+ - **Synoptis TrustIdentity**: New module for canonical `agent://` URI handling
42
+
43
+ ---
44
+
7
45
  ## [2.10.1] - 2026-03-19
8
46
 
9
47
  ### Fixed
@@ -438,6 +476,9 @@ This project follows [Semantic Versioning](https://semver.org/).
438
476
  - Skill promotion with Persona Assembly
439
477
  - Tool guide and metadata system
440
478
 
479
+ [3.0.0]: https://github.com/masaomi/KairosChain_2026/compare/v2.10.1...v3.0.0
480
+ [2.10.1]: https://github.com/masaomi/KairosChain_2026/compare/v2.10.0...v2.10.1
481
+ [2.10.0]: https://github.com/masaomi/KairosChain_2026/compare/v2.9.0...v2.10.0
441
482
  [2.9.0]: https://github.com/masaomi/KairosChain_2026/compare/v2.8.0...v2.9.0
442
483
  [2.8.0]: https://github.com/masaomi/KairosChain_2026/compare/v2.7.0...v2.8.0
443
484
  [2.7.0]: https://github.com/masaomi/KairosChain_2026/compare/v2.6.0...v2.7.0
data/bin/kairos-chain CHANGED
@@ -6,7 +6,7 @@
6
6
  Encoding.default_external = Encoding::UTF_8
7
7
  Encoding.default_internal = Encoding::UTF_8
8
8
 
9
- # KairosChain - Memory-driven agent framework
9
+ # KairosChain - Self-referential MCP server for auditable skill self-management
10
10
  #
11
11
  # Usage:
12
12
  # kairos-chain # stdio mode (default, for Cursor local)
@@ -81,8 +81,9 @@ module KairosMcp
81
81
  # @param role [String] Role: "owner", "member", or "guest"
82
82
  # @param issued_by [String] Who issued this token
83
83
  # @param expires_in [String, nil] Expiry duration: "90d", "24h", "never", or nil (default)
84
+ # @param pubkey_hash [String, nil] SHA256 hex of agent's public key (for Service Grant)
84
85
  # @return [Hash] { raw_token:, token_hash:, user:, role:, ... }
85
- def create(user:, role: 'member', issued_by: 'system', expires_in: nil)
86
+ def create(user:, role: 'member', issued_by: 'system', expires_in: nil, pubkey_hash: nil)
86
87
  validate_role!(role)
87
88
  validate_user!(user)
88
89
 
@@ -101,6 +102,7 @@ module KairosMcp
101
102
  'issued_by' => issued_by,
102
103
  'status' => 'active'
103
104
  }
105
+ entry['pubkey_hash'] = pubkey_hash if pubkey_hash
104
106
 
105
107
  @tokens << entry
106
108
  save_tokens
@@ -120,12 +122,14 @@ module KairosMcp
120
122
  return nil if entry['status'] != 'active'
121
123
  return nil if expired?(entry)
122
124
 
123
- {
125
+ result = {
124
126
  user: entry['user'],
125
127
  role: entry['role'],
126
128
  issued_at: entry['issued_at'],
127
129
  expires_at: entry['expires_at']
128
130
  }
131
+ result[:pubkey_hash] = entry['pubkey_hash'] if entry['pubkey_hash']
132
+ result
129
133
  end
130
134
 
131
135
  # Revoke a user's token(s)
@@ -213,6 +213,16 @@ module KairosMcp
213
213
  # so that @initialized=true and tools are available.
214
214
  # See L1 knowledge: kairoschain_operations "Streamable HTTP Transport: Stateless Design"
215
215
  user_context = auth_result.user_context
216
+
217
+ # Inject remote_ip for Service Grant IP rate limiting (D-5).
218
+ # Uses shared ClientIpResolver when available (Path A/B consistency).
219
+ if user_context
220
+ user_context[:remote_ip] = if defined?(ServiceGrant) && ServiceGrant.respond_to?(:ip_resolver) && ServiceGrant.ip_resolver
221
+ ServiceGrant.ip_resolver.resolve(env)
222
+ else
223
+ env['HTTP_X_REAL_IP'] || env['REMOTE_ADDR']
224
+ end
225
+ end
216
226
  protocol = Protocol.new(user_context: user_context)
217
227
 
218
228
  if method == 'initialize'
@@ -83,6 +83,14 @@ module KairosMcp
83
83
  policy ? policy.call(@current_user) : true
84
84
  end
85
85
 
86
+ def can_manage_grants?
87
+ return true unless @current_user
88
+ policy = self.class.policy_for(:can_manage_grants)
89
+ # Default: deny (unlike can_manage_tokens? which defaults to allow).
90
+ # Service Grant admin ops should be blocked if the policy SkillSet is not loaded.
91
+ policy ? policy.call(@current_user) : false
92
+ end
93
+
86
94
  # Set workspace root from MCP client (roots) or environment
87
95
  def set_workspace(roots = nil)
88
96
  if roots && roots.is_a?(Array) && !roots.empty?
@@ -153,6 +153,7 @@ module KairosMcp
153
153
  {
154
154
  "mcpServers": {
155
155
  "kairos": {
156
+ "type": "http",
156
157
  "url": "http://<server-host>:<port>/mcp",
157
158
  "headers": {
158
159
  "Authorization": "Bearer #{result['raw_token']}"
@@ -1,4 +1,4 @@
1
1
  module KairosMcp
2
- VERSION = "2.10.1"
2
+ VERSION = "3.0.0"
3
3
  CHANGELOG_URL = "https://github.com/masaomi/KairosChain_2026/blob/main/CHANGELOG.md"
4
4
  end
@@ -554,6 +554,7 @@ Add to `~/.cursor/mcp.json`:
554
554
  {
555
555
  "mcpServers": {
556
556
  "kairos-chain-http": {
557
+ "type": "http",
557
558
  "url": "http://localhost:8080/mcp",
558
559
  "headers": {
559
560
  "Authorization": "Bearer kc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
@@ -803,6 +804,7 @@ After setting up HTTPS, update `~/.cursor/mcp.json`:
803
804
  {
804
805
  "mcpServers": {
805
806
  "kairos-chain-http": {
807
+ "type": "http",
806
808
  "url": "https://kairos.example.com/mcp",
807
809
  "headers": {
808
810
  "Authorization": "Bearer kc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
@@ -538,6 +538,7 @@ ruby bin/kairos-chain --http --host 127.0.0.1 --port 8080
538
538
  {
539
539
  "mcpServers": {
540
540
  "kairos-chain-http": {
541
+ "type": "http",
541
542
  "url": "http://localhost:8080/mcp",
542
543
  "headers": {
543
544
  "Authorization": "Bearer kc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
@@ -787,6 +788,7 @@ HTTPSをセットアップ後、`~/.cursor/mcp.json`を更新:
787
788
  {
788
789
  "mcpServers": {
789
790
  "kairos-chain-http": {
791
+ "type": "http",
790
792
  "url": "https://kairos.example.com/mcp",
791
793
  "headers": {
792
794
  "Authorization": "Bearer kc_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
@@ -0,0 +1,108 @@
1
+ ---
2
+ description: Service Grant SkillSet — generic access control, usage tracking, and billing for KairosChain services
3
+ tags: [documentation, readme, service-grant, access-control, billing, payment, subscription]
4
+ readme_order: 4.9
5
+ readme_lang: en
6
+ ---
7
+
8
+ # Service Grant SkillSet
9
+
10
+ Service Grant provides **generic, service-independent access control and billing** for any KairosChain-based service. It manages "what is permitted" for authenticated agents without managing identity (identity = RSA key pair via MMP).
11
+
12
+ ## Key Concepts
13
+
14
+ - **pubkey_hash**: SHA-256 hash of agent's public key — this IS the identity
15
+ - **Grant**: Per-(pubkey_hash, service) entry with plan, status, and usage
16
+ - **Plan**: Quota/billing configuration (free, pro, etc.) defined in YAML
17
+ - **Dual-path enforcement**: Both `/mcp` (AccessGate) and `/place/*` (PlaceMiddleware) are gated
18
+
19
+ ## Architecture
20
+
21
+ ```
22
+ Path A (/mcp): Token -> pubkey_hash -> AccessGate -> AccessChecker
23
+ Path B (/place): Bearer -> peer_id -> PlaceMiddleware -> AccessChecker
24
+
25
+ AccessChecker pipeline:
26
+ suspension -> cooldown -> expiry -> trust -> quota
27
+ ```
28
+
29
+ ### Components
30
+
31
+ | Component | Responsibility |
32
+ |-----------|---------------|
33
+ | **GrantManager** | Grant lifecycle (create, upgrade, suspend, downgrade) |
34
+ | **AccessChecker** | Unified access decision pipeline |
35
+ | **UsageTracker** | Atomic quota consumption with cycle management |
36
+ | **PlanRegistry** | YAML config loader with validation |
37
+ | **PaymentVerifier** | Cryptographic payment attestation verification |
38
+ | **PgConnectionPool** | Thread-safe PostgreSQL with circuit breaker |
39
+ | **TrustScorerAdapter** | Synoptis trust score integration with caching |
40
+
41
+ ## Billing Models
42
+
43
+ ```yaml
44
+ billing_model: free # No charges
45
+ billing_model: per_action # Pay per API call
46
+ billing_model: metered # Usage-based with cycle tracking
47
+ billing_model: subscription # Time-based with auto-expiry
48
+ ```
49
+
50
+ ## Payment Flow (Proof-Centric)
51
+
52
+ ```
53
+ Payment Agent (external) creates attestation proof
54
+ -> PaymentVerifier verifies: signature, issuer, freshness, amount, nonce
55
+ -> Atomic transaction: ensure_grant + upgrade_plan + record_payment
56
+ -> Subscription expiry auto-managed (lazy downgrade on access check)
57
+ ```
58
+
59
+ Payment verification uses Synoptis ProofEnvelope — the same cryptographic attestation infrastructure used for trust scoring.
60
+
61
+ ## Anti-Sybil Measures
62
+
63
+ - IP rate limiting (5 new grants/hour per IP, PostgreSQL-backed)
64
+ - Delayed activation cooldown (5 min for write operations)
65
+ - Synoptis trust score requirements (configurable per action)
66
+ - Anti-collusion PageRank with external attestation weighting
67
+
68
+ ## MCP Tools
69
+
70
+ | Tool | Access | Description |
71
+ |------|--------|-------------|
72
+ | `service_grant_status` | All users | View grants and usage |
73
+ | `service_grant_manage` | Owner only | Plan changes, suspend/unsuspend |
74
+ | `service_grant_migrate` | Owner only | Database schema migrations |
75
+ | `service_grant_pay` | All users | Submit payment attestation proofs |
76
+
77
+ ## Configuration Example
78
+
79
+ ```yaml
80
+ services:
81
+ meeting_place:
82
+ billing_model: per_action
83
+ currency: USD
84
+ cycle: monthly
85
+ write_actions: [deposit_skill]
86
+ action_map:
87
+ meeting_deposit: deposit_skill
88
+ meeting_browse: browse
89
+ plans:
90
+ free:
91
+ limits:
92
+ deposit_skill: 5
93
+ browse: -1 # unlimited
94
+ trust_requirements:
95
+ deposit_skill: 0.1
96
+ pro:
97
+ subscription_price: "9.99"
98
+ subscription_duration: 30 # days
99
+ limits:
100
+ deposit_skill: -1
101
+ browse: -1
102
+ ```
103
+
104
+ ## Dependencies
105
+
106
+ - **Hard**: PostgreSQL (grants, usage, payments)
107
+ - **Hard**: Synoptis SkillSet (attestation verification, trust scoring)
108
+ - **Soft**: Hestia SkillSet (Meeting Place middleware integration)
@@ -0,0 +1,108 @@
1
+ ---
2
+ description: Service Grant SkillSet — KairosChainサービス向け汎用アクセス制御・使用量追跡・課金
3
+ tags: [documentation, readme, service-grant, access-control, billing, payment, subscription]
4
+ readme_order: 4.9
5
+ readme_lang: jp
6
+ ---
7
+
8
+ # Service Grant SkillSet
9
+
10
+ Service Grantは、KairosChainベースのあらゆるサービスに対する**汎用的かつサービス非依存のアクセス制御・課金**を提供します。認証済みエージェントに対して「何が許可されているか」を管理し、アイデンティティ管理は行いません(アイデンティティ = MMP経由のRSA鍵ペア)。
11
+
12
+ ## 主要概念
13
+
14
+ - **pubkey_hash**: エージェントの公開鍵のSHA-256ハッシュ — これがアイデンティティ
15
+ - **Grant**: (pubkey_hash, service)ごとのエントリ(プラン、ステータス、使用量)
16
+ - **Plan**: YAML定義のクォータ/課金設定(free, pro等)
17
+ - **デュアルパス適用**: `/mcp`(AccessGate)と `/place/*`(PlaceMiddleware)の両方をゲート
18
+
19
+ ## アーキテクチャ
20
+
21
+ ```
22
+ パスA (/mcp): Token -> pubkey_hash -> AccessGate -> AccessChecker
23
+ パスB (/place): Bearer -> peer_id -> PlaceMiddleware -> AccessChecker
24
+
25
+ AccessCheckerパイプライン:
26
+ 停止確認 -> クールダウン -> 有効期限 -> 信頼スコア -> クォータ
27
+ ```
28
+
29
+ ### コンポーネント
30
+
31
+ | コンポーネント | 責務 |
32
+ |--------------|------|
33
+ | **GrantManager** | Grant ライフサイクル(作成、アップグレード、停止、ダウングレード) |
34
+ | **AccessChecker** | 統合アクセス判定パイプライン |
35
+ | **UsageTracker** | サイクル管理付きアトミッククォータ消費 |
36
+ | **PlanRegistry** | バリデーション付きYAML設定ローダー |
37
+ | **PaymentVerifier** | 暗号学的支払い証明の検証 |
38
+ | **PgConnectionPool** | サーキットブレーカー付きスレッドセーフPostgreSQL |
39
+ | **TrustScorerAdapter** | キャッシュ付きSynoptis信頼スコア統合 |
40
+
41
+ ## 課金モデル
42
+
43
+ ```yaml
44
+ billing_model: free # 無料
45
+ billing_model: per_action # APIコール単位課金
46
+ billing_model: metered # 使用量ベース(サイクル内追跡)
47
+ billing_model: subscription # 期間ベース(自動期限管理)
48
+ ```
49
+
50
+ ## 支払いフロー(証明中心設計)
51
+
52
+ ```
53
+ Payment Agent(外部)が証明(attestation proof)を作成
54
+ -> PaymentVerifierが検証: 署名、発行者、鮮度、金額、ノンス
55
+ -> アトミックトランザクション: ensure_grant + upgrade_plan + record_payment
56
+ -> サブスクリプション期限は自動管理(アクセス時の遅延ダウングレード)
57
+ ```
58
+
59
+ 支払い検証にはSynoptis ProofEnvelope — 信頼スコアリングと同じ暗号学的証明インフラ — を使用します。
60
+
61
+ ## アンチシビル対策
62
+
63
+ - IP レート制限(IP あたり 5 新規 Grant/時間、PostgreSQL バック)
64
+ - 遅延アクティベーションクールダウン(書き込み操作に5分)
65
+ - Synoptis 信頼スコア要件(アクション単位で設定可能)
66
+ - 外部証明重み付けのアンチ共謀 PageRank
67
+
68
+ ## MCPツール
69
+
70
+ | ツール | アクセス | 説明 |
71
+ |-------|---------|------|
72
+ | `service_grant_status` | 全ユーザー | Grant と使用状況の表示 |
73
+ | `service_grant_manage` | オーナーのみ | プラン変更、停止/再開 |
74
+ | `service_grant_migrate` | オーナーのみ | データベーススキーマ移行 |
75
+ | `service_grant_pay` | 全ユーザー | 支払い証明の提出 |
76
+
77
+ ## 設定例
78
+
79
+ ```yaml
80
+ services:
81
+ meeting_place:
82
+ billing_model: per_action
83
+ currency: USD
84
+ cycle: monthly
85
+ write_actions: [deposit_skill]
86
+ action_map:
87
+ meeting_deposit: deposit_skill
88
+ meeting_browse: browse
89
+ plans:
90
+ free:
91
+ limits:
92
+ deposit_skill: 5
93
+ browse: -1 # 無制限
94
+ trust_requirements:
95
+ deposit_skill: 0.1
96
+ pro:
97
+ subscription_price: "9.99"
98
+ subscription_duration: 30 # 日
99
+ limits:
100
+ deposit_skill: -1
101
+ browse: -1
102
+ ```
103
+
104
+ ## 依存関係
105
+
106
+ - **必須**: PostgreSQL(Grant、使用量、支払い記録)
107
+ - **必須**: Synoptis SkillSet(証明検証、信頼スコアリング)
108
+ - **任意**: Hestia SkillSet(Meeting Place ミドルウェア統合)
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'digest'
3
4
  require 'json'
4
5
  require 'uri'
5
6
 
@@ -20,6 +21,37 @@ module Hestia
20
21
  'Cache-Control' => 'no-cache'
21
22
  }.freeze
22
23
 
24
+ # Maps HTTP route segments to abstract action names for access control.
25
+ # Service Grant (or any middleware) uses these for gating.
26
+ ROUTE_ACTION_MAP = {
27
+ 'deposit' => 'deposit_skill',
28
+ 'browse' => 'browse',
29
+ 'skill_content' => 'browse',
30
+ 'needs' => 'browse',
31
+ 'agents' => 'browse',
32
+ 'keys' => 'browse',
33
+ 'acquire' => 'acquire_skill',
34
+ 'unregister' => 'unregister',
35
+ }.freeze
36
+
37
+ # Place middleware registry (class-level, thread-safe)
38
+ @place_middlewares = []
39
+ @middleware_mutex = Mutex.new
40
+
41
+ class << self
42
+ def register_middleware(middleware)
43
+ @middleware_mutex.synchronize { @place_middlewares << middleware }
44
+ end
45
+
46
+ def unregister_middleware(middleware)
47
+ @middleware_mutex.synchronize { @place_middlewares.delete(middleware) }
48
+ end
49
+
50
+ def place_middlewares
51
+ @middleware_mutex.synchronize { @place_middlewares.dup }
52
+ end
53
+ end
54
+
23
55
  attr_reader :registry, :skill_board, :heartbeat_manager, :session_store, :started_at
24
56
 
25
57
  def initialize(config: nil)
@@ -107,7 +139,30 @@ module Hestia
107
139
 
108
140
  # All other endpoints require Bearer token
109
141
  auth_result = authenticate!(env)
110
- return auth_result unless auth_result.nil?
142
+ # authenticate! returns { peer_id:, auth_token: } on success,
143
+ # or a Rack response [status, headers, body] on failure.
144
+ if auth_result.is_a?(Array)
145
+ return auth_result
146
+ end
147
+ peer_id = auth_result[:peer_id]
148
+ auth_token = auth_result[:auth_token]
149
+
150
+ # Resolve action from route for middleware
151
+ route_segment = extract_route_segment(path)
152
+ action = resolve_action(route_segment)
153
+
154
+ # Run place middlewares (Service Grant, etc.)
155
+ # Resolve remote_ip via ServiceGrant's ClientIpResolver if available
156
+ remote_ip = if defined?(ServiceGrant) && ServiceGrant.respond_to?(:ip_resolver) && ServiceGrant.ip_resolver
157
+ ServiceGrant.ip_resolver.resolve(env)
158
+ else
159
+ env['REMOTE_ADDR']
160
+ end
161
+ denial = run_place_middlewares(peer_id, action, service_name,
162
+ auth_token: auth_token, remote_ip: remote_ip)
163
+ if denial
164
+ return [denial[:status] || 403, JSON_HEADERS, [denial.to_json]]
165
+ end
111
166
 
112
167
  case [request_method, path]
113
168
  when ['POST', '/place/v1/unregister']
@@ -227,7 +282,8 @@ module Hestia
227
282
  # Issue session token if signature was verified
228
283
  session_token = nil
229
284
  if verified
230
- session_token = @session_store.create_session(agent_id, public_key)
285
+ pubkey_hash = public_key ? Digest::SHA256.hexdigest(public_key) : nil
286
+ session_token = @session_store.create_session(agent_id, public_key, pubkey_hash: pubkey_hash)
231
287
  end
232
288
 
233
289
  response = result.merge(identity_verified: verified)
@@ -455,6 +511,11 @@ module Hestia
455
511
 
456
512
  # --- Auth ---
457
513
 
514
+ # Authenticate the request and return identity on success.
515
+ # Returns a Rack response array on failure.
516
+ #
517
+ # @param env [Hash] Rack environment
518
+ # @return [Hash, Array] { peer_id:, auth_token: } on success, Rack response on failure
458
519
  def authenticate!(env)
459
520
  token = extract_bearer_token(env)
460
521
  unless token
@@ -479,7 +540,7 @@ module Hestia
479
540
  # Update heartbeat on any authenticated request
480
541
  @heartbeat_manager.touch(peer_id)
481
542
 
482
- nil # Auth passed
543
+ { peer_id: peer_id, auth_token: token }
483
544
  end
484
545
 
485
546
  def extract_bearer_token(env)
@@ -519,5 +580,37 @@ module Hestia
519
580
  def json_response(status, body)
520
581
  [status, JSON_HEADERS, [body.to_json]]
521
582
  end
583
+
584
+ # Extract route segment from path for action mapping.
585
+ # For parameterized routes (/keys/:id, /skill_content/:id), falls back to
586
+ # the first segment when the last segment is not a known action.
587
+ def extract_route_segment(path)
588
+ segments = path.sub('/place/v1/', '').split('/')
589
+ candidate = segments.last || ''
590
+ ROUTE_ACTION_MAP.key?(candidate) ? candidate : (segments.first || '')
591
+ end
592
+
593
+ # Map route segment to abstract action name for access control.
594
+ def resolve_action(route_segment)
595
+ ROUTE_ACTION_MAP[route_segment] || route_segment
596
+ end
597
+
598
+ # Run registered place middlewares. Returns nil if all pass,
599
+ # or a denial Hash if any middleware denies.
600
+ def run_place_middlewares(peer_id, action, service, auth_token: nil, remote_ip: nil)
601
+ self.class.place_middlewares.each do |mw|
602
+ # Inject session_store if middleware accepts it (e.g., ServiceGrant::PlaceMiddleware)
603
+ mw.session_store = @session_store if mw.respond_to?(:session_store=)
604
+ result = mw.check(peer_id: peer_id, action: action, service: service,
605
+ auth_token: auth_token, remote_ip: remote_ip)
606
+ return result if result
607
+ end
608
+ nil
609
+ end
610
+
611
+ # Service name for this Meeting Place instance
612
+ def service_name
613
+ @config.dig('meeting_place', 'service_name') || 'meeting_place'
614
+ end
522
615
  end
523
616
  end
@@ -19,7 +19,7 @@ module MMP
19
19
  DEFAULT_RATE_LIMIT_PER_MINUTE = 100
20
20
 
21
21
  Session = Struct.new(
22
- :peer_id, :public_key, :token, :created_at,
22
+ :peer_id, :public_key, :pubkey_hash, :token, :created_at,
23
23
  :last_activity, :request_count, :request_window_start,
24
24
  keyword_init: true
25
25
  )
@@ -28,6 +28,7 @@ module MMP
28
28
 
29
29
  def initialize(ttl_minutes: DEFAULT_TTL_MINUTES, rate_limit: DEFAULT_RATE_LIMIT_PER_MINUTE)
30
30
  @sessions = {}
31
+ @peer_id_index = {} # peer_id => token (most recent session)
31
32
  @ttl = ttl_minutes * 60
32
33
  @ttl_minutes = ttl_minutes
33
34
  @rate_limit = rate_limit
@@ -36,19 +37,26 @@ module MMP
36
37
 
37
38
  # Create a new session for a verified peer.
38
39
  # Returns the session token string.
39
- def create_session(peer_id, public_key)
40
+ #
41
+ # @param peer_id [String] Peer identifier
42
+ # @param public_key [String] PEM-encoded public key
43
+ # @param pubkey_hash [String, nil] SHA256 hex of public key (for Service Grant)
44
+ def create_session(peer_id, public_key, pubkey_hash: nil)
40
45
  token = SecureRandom.hex(32)
41
46
  now = Time.now
42
47
  @mutex.synchronize do
43
48
  @sessions[token] = Session.new(
44
49
  peer_id: peer_id,
45
50
  public_key: public_key,
51
+ pubkey_hash: pubkey_hash,
46
52
  token: token,
47
53
  created_at: now,
48
54
  last_activity: now,
49
55
  request_count: 0,
50
56
  request_window_start: now
51
57
  )
58
+ # Update peer_id reverse index (most recent session wins)
59
+ @peer_id_index[peer_id] = token
52
60
  end
53
61
  token
54
62
  end
@@ -125,12 +133,40 @@ module MMP
125
133
  end
126
134
  end
127
135
 
136
+ # Reverse lookup: find pubkey_hash for a given peer_id.
137
+ # Uses O(1) index instead of linear scan.
138
+ #
139
+ # @param peer_id [String] Peer identifier
140
+ # @return [String, nil] pubkey_hash if found
141
+ def pubkey_hash_for(peer_id)
142
+ @mutex.synchronize do
143
+ token = @peer_id_index[peer_id]
144
+ return @sessions[token]&.pubkey_hash if token && @sessions[token]
145
+ nil
146
+ end
147
+ end
148
+
149
+ # Exact session lookup by token. O(1) — no ambiguity with multiple sessions.
150
+ #
151
+ # @param token [String] Bearer token
152
+ # @return [String, nil] pubkey_hash if session exists
153
+ def pubkey_hash_for_token(token)
154
+ @mutex.synchronize do
155
+ session = @sessions[token]
156
+ session&.pubkey_hash
157
+ end
158
+ end
159
+
128
160
  private
129
161
 
130
162
  # Remove expired sessions (called within mutex).
131
163
  def cleanup_expired
132
164
  now = Time.now
133
- @sessions.delete_if { |_token, session| now - session.created_at > @ttl }
165
+ @sessions.delete_if do |_token, session|
166
+ expired = now - session.created_at > @ttl
167
+ @peer_id_index.delete(session.peer_id) if expired && @peer_id_index[session.peer_id] == _token
168
+ expired
169
+ end
134
170
  end
135
171
  end
136
172
  end