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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +41 -0
- data/bin/kairos-chain +1 -1
- data/lib/kairos_mcp/auth/token_store.rb +6 -2
- data/lib/kairos_mcp/http_server.rb +10 -0
- data/lib/kairos_mcp/safety.rb +8 -0
- data/lib/kairos_mcp/tools/token_manage.rb +1 -0
- data/lib/kairos_mcp/version.rb +1 -1
- data/templates/knowledge/kairoschain_setup/kairoschain_setup.md +2 -0
- data/templates/knowledge/kairoschain_setup_jp/kairoschain_setup_jp.md +2 -0
- data/templates/knowledge/service_grant_access_control/service_grant_access_control.md +108 -0
- data/templates/knowledge/service_grant_access_control_jp/service_grant_access_control_jp.md +108 -0
- data/templates/skillsets/hestia/lib/hestia/place_router.rb +96 -3
- data/templates/skillsets/mmp/lib/mmp/meeting_session_store.rb +39 -3
- data/templates/skillsets/service_grant/config/service_grant.yml +118 -0
- data/templates/skillsets/service_grant/knowledge/service_grant_guide/service_grant_guide.md +106 -0
- data/templates/skillsets/service_grant/lib/service_grant/access_checker.rb +89 -0
- data/templates/skillsets/service_grant/lib/service_grant/access_gate.rb +65 -0
- data/templates/skillsets/service_grant/lib/service_grant/client_ip_resolver.rb +25 -0
- data/templates/skillsets/service_grant/lib/service_grant/cycle_manager.rb +41 -0
- data/templates/skillsets/service_grant/lib/service_grant/errors.rb +24 -0
- data/templates/skillsets/service_grant/lib/service_grant/grant_manager.rb +212 -0
- data/templates/skillsets/service_grant/lib/service_grant/ip_rate_tracker.rb +74 -0
- data/templates/skillsets/service_grant/lib/service_grant/payment_verifier.rb +301 -0
- data/templates/skillsets/service_grant/lib/service_grant/pg_circuit_breaker.rb +108 -0
- data/templates/skillsets/service_grant/lib/service_grant/pg_connection_pool.rb +119 -0
- data/templates/skillsets/service_grant/lib/service_grant/place_middleware.rb +55 -0
- data/templates/skillsets/service_grant/lib/service_grant/plan_registry.rb +127 -0
- data/templates/skillsets/service_grant/lib/service_grant/request_enricher.rb +26 -0
- data/templates/skillsets/service_grant/lib/service_grant/trust_scorer_adapter.rb +78 -0
- data/templates/skillsets/service_grant/lib/service_grant/usage_tracker.rb +118 -0
- data/templates/skillsets/service_grant/lib/service_grant.rb +248 -0
- data/templates/skillsets/service_grant/migrations/001_service_grant_schema.sql +92 -0
- data/templates/skillsets/service_grant/migrations/002_grant_ip_events.sql +11 -0
- data/templates/skillsets/service_grant/migrations/003_subscription_and_provider_tx.sql +20 -0
- data/templates/skillsets/service_grant/skillset.json +18 -0
- data/templates/skillsets/service_grant/test/test_service_grant.rb +2546 -0
- data/templates/skillsets/service_grant/tools/service_grant_manage.rb +139 -0
- data/templates/skillsets/service_grant/tools/service_grant_migrate.rb +150 -0
- data/templates/skillsets/service_grant/tools/service_grant_pay.rb +60 -0
- data/templates/skillsets/service_grant/tools/service_grant_status.rb +98 -0
- data/templates/skillsets/synoptis/lib/synoptis/trust_identity.rb +53 -0
- data/templates/skillsets/synoptis/lib/synoptis/trust_scorer.rb +207 -7
- metadata +40 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b831eba22baf94d43c7592c5ff2dbf12ca928dcea034b713837bf5253e691f99
|
|
4
|
+
data.tar.gz: 65c6cd548075ccfccf85d3330548fe51280d39f7c767f6de96c8d64fa5afd799
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 -
|
|
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'
|
data/lib/kairos_mcp/safety.rb
CHANGED
|
@@ -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?
|
data/lib/kairos_mcp/version.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|