capsium 0.2.0 → 0.6.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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +17 -1
  3. data/CHANGELOG.md +322 -0
  4. data/README.adoc +643 -7
  5. data/capsium.gemspec +3 -0
  6. data/lib/capsium/cli/formatting.rb +27 -0
  7. data/lib/capsium/cli/package.rb +106 -7
  8. data/lib/capsium/cli/reactor.rb +120 -6
  9. data/lib/capsium/cli.rb +30 -0
  10. data/lib/capsium/log_buffer.rb +47 -0
  11. data/lib/capsium/package/authentication.rb +38 -0
  12. data/lib/capsium/package/authentication_config.rb +78 -0
  13. data/lib/capsium/package/bundle.rb +136 -0
  14. data/lib/capsium/package/cipher.rb +253 -0
  15. data/lib/capsium/package/composition.rb +93 -0
  16. data/lib/capsium/package/dataset.rb +20 -0
  17. data/lib/capsium/package/dependency_resolver.rb +91 -0
  18. data/lib/capsium/package/encryption_config.rb +42 -0
  19. data/lib/capsium/package/manifest.rb +1 -1
  20. data/lib/capsium/package/merged_view.rb +170 -0
  21. data/lib/capsium/package/metadata.rb +1 -1
  22. data/lib/capsium/package/metadata_config.rb +9 -1
  23. data/lib/capsium/package/open_pgp.rb +65 -0
  24. data/lib/capsium/package/open_pgp_cipher.rb +66 -0
  25. data/lib/capsium/package/open_pgp_signer.rb +176 -0
  26. data/lib/capsium/package/preparation.rb +81 -0
  27. data/lib/capsium/package/routes_config.rb +60 -6
  28. data/lib/capsium/package/security.rb +21 -4
  29. data/lib/capsium/package/security_config.rb +6 -2
  30. data/lib/capsium/package/signer.rb +216 -0
  31. data/lib/capsium/package/storage_config.rb +29 -1
  32. data/lib/capsium/package/store.rb +136 -0
  33. data/lib/capsium/package/testing/config_test.rb +70 -0
  34. data/lib/capsium/package/testing/context.rb +11 -0
  35. data/lib/capsium/package/testing/data_validation_test.rb +63 -0
  36. data/lib/capsium/package/testing/file_test.rb +45 -0
  37. data/lib/capsium/package/testing/report.rb +33 -0
  38. data/lib/capsium/package/testing/route_test.rb +64 -0
  39. data/lib/capsium/package/testing/test_case.rb +60 -0
  40. data/lib/capsium/package/testing/test_suite.rb +86 -0
  41. data/lib/capsium/package/testing.rb +20 -0
  42. data/lib/capsium/package/validator.rb +39 -3
  43. data/lib/capsium/package/verification.rb +34 -0
  44. data/lib/capsium/package/version.rb +204 -0
  45. data/lib/capsium/package.rb +78 -75
  46. data/lib/capsium/packager.rb +97 -22
  47. data/lib/capsium/reactor/authenticator.rb +180 -0
  48. data/lib/capsium/reactor/content_api.rb +110 -0
  49. data/lib/capsium/reactor/data_api.rb +154 -0
  50. data/lib/capsium/reactor/deploy.rb +71 -0
  51. data/lib/capsium/reactor/endpoints.rb +37 -0
  52. data/lib/capsium/reactor/graphql_api.rb +123 -0
  53. data/lib/capsium/reactor/graphql_schema.rb +138 -0
  54. data/lib/capsium/reactor/htpasswd.rb +154 -0
  55. data/lib/capsium/reactor/introspection.rb +151 -31
  56. data/lib/capsium/reactor/metrics.rb +30 -0
  57. data/lib/capsium/reactor/mount.rb +206 -0
  58. data/lib/capsium/reactor/oauth2.rb +106 -0
  59. data/lib/capsium/reactor/overlay.rb +248 -0
  60. data/lib/capsium/reactor/package_saver.rb +99 -0
  61. data/lib/capsium/reactor/responses.rb +41 -0
  62. data/lib/capsium/reactor/serving.rb +154 -0
  63. data/lib/capsium/reactor/session.rb +82 -0
  64. data/lib/capsium/reactor.rb +116 -67
  65. data/lib/capsium/registry/local.rb +120 -0
  66. data/lib/capsium/registry/remote.rb +105 -0
  67. data/lib/capsium/registry.rb +190 -0
  68. data/lib/capsium/thor_ext.rb +1 -1
  69. data/lib/capsium/version.rb +1 -1
  70. data/lib/capsium.rb +2 -0
  71. data/sig/capsium/log_buffer.rbs +30 -0
  72. data/sig/capsium/package/authentication.rbs +23 -0
  73. data/sig/capsium/package/authentication_config.rbs +71 -0
  74. data/sig/capsium/package/bundle.rbs +43 -0
  75. data/sig/capsium/package/cipher.rbs +62 -0
  76. data/sig/capsium/package/composition.rbs +21 -0
  77. data/sig/capsium/package/dataset.rbs +8 -0
  78. data/sig/capsium/package/dependency_resolver.rbs +53 -0
  79. data/sig/capsium/package/encryption_config.rbs +39 -0
  80. data/sig/capsium/package/merged_view.rbs +47 -0
  81. data/sig/capsium/package/metadata.rbs +1 -0
  82. data/sig/capsium/package/open_pgp.rbs +38 -0
  83. data/sig/capsium/package/open_pgp_cipher.rbs +15 -0
  84. data/sig/capsium/package/open_pgp_signer.rbs +46 -0
  85. data/sig/capsium/package/preparation.rbs +23 -0
  86. data/sig/capsium/package/routes_config.rbs +36 -3
  87. data/sig/capsium/package/security.rbs +9 -2
  88. data/sig/capsium/package/security_config.rbs +6 -3
  89. data/sig/capsium/package/signer.rbs +66 -0
  90. data/sig/capsium/package/storage_config.rbs +26 -2
  91. data/sig/capsium/package/store.rbs +43 -0
  92. data/sig/capsium/package/testing/config_test.rbs +21 -0
  93. data/sig/capsium/package/testing/context.rbs +14 -0
  94. data/sig/capsium/package/testing/data_validation_test.rbs +22 -0
  95. data/sig/capsium/package/testing/file_test.rbs +17 -0
  96. data/sig/capsium/package/testing/report.rbs +20 -0
  97. data/sig/capsium/package/testing/route_test.rbs +22 -0
  98. data/sig/capsium/package/testing/test_case.rbs +41 -0
  99. data/sig/capsium/package/testing/test_suite.rbs +20 -0
  100. data/sig/capsium/package/testing.rbs +7 -0
  101. data/sig/capsium/package/verification.rbs +26 -0
  102. data/sig/capsium/package/version.rbs +39 -0
  103. data/sig/capsium/package.rbs +29 -16
  104. data/sig/capsium/packager.rbs +9 -0
  105. data/sig/capsium/reactor/authenticator.rbs +40 -0
  106. data/sig/capsium/reactor/content_api.rbs +19 -0
  107. data/sig/capsium/reactor/data_api.rbs +23 -0
  108. data/sig/capsium/reactor/deploy.rbs +34 -0
  109. data/sig/capsium/reactor/endpoints.rbs +16 -0
  110. data/sig/capsium/reactor/graphql_api.rbs +27 -0
  111. data/sig/capsium/reactor/graphql_schema.rbs +20 -0
  112. data/sig/capsium/reactor/htpasswd.rbs +36 -0
  113. data/sig/capsium/reactor/introspection.rbs +27 -7
  114. data/sig/capsium/reactor/metrics.rbs +13 -0
  115. data/sig/capsium/reactor/mount.rbs +83 -0
  116. data/sig/capsium/reactor/oauth2.rbs +31 -0
  117. data/sig/capsium/reactor/overlay.rbs +72 -0
  118. data/sig/capsium/reactor/package_saver.rbs +12 -0
  119. data/sig/capsium/reactor/responses.rbs +23 -0
  120. data/sig/capsium/reactor/serving.rbs +46 -0
  121. data/sig/capsium/reactor/session.rbs +36 -0
  122. data/sig/capsium/reactor.rbs +22 -2
  123. data/sig/capsium/registry/local.rbs +16 -0
  124. data/sig/capsium/registry/remote.rbs +18 -0
  125. data/sig/capsium/registry.rbs +83 -0
  126. data/sig/graphql.rbs +12 -0
  127. metadata +135 -1
@@ -0,0 +1,27 @@
1
+ module Capsium
2
+ class Reactor
3
+ # GraphQL over a mount's datasets (POST and GET <mount>/graphql):
4
+ # executes the derived schema and maps typed overlay errors into
5
+ # the "errors" array.
6
+ class GraphqlApi
7
+ include Responses
8
+
9
+ PATH: String
10
+
11
+ def self.path?: (String inner_path) -> bool
12
+
13
+ def initialize: (Mount mount) -> void
14
+
15
+ def handle: (untyped request, untyped response) -> untyped
16
+
17
+ # Query/mutation resolvers called from the derived schema.
18
+ def collection: (Package::Dataset dataset, ?id: String? id) -> Array[untyped]
19
+
20
+ def create_item: (Package::Dataset dataset, item: untyped item) -> untyped
21
+
22
+ def update_item: (Package::Dataset dataset, id: String id, item: untyped item) -> untyped
23
+
24
+ def remove_item: (Package::Dataset dataset, id: String id) -> bool
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,20 @@
1
+ module Capsium
2
+ class Reactor
3
+ # Builds the GraphQL schema for a mounted package: per dataset a
4
+ # query field "<dataset>" (list, optional "id:") plus
5
+ # create<Dataset>/update<Dataset>/delete<Dataset> mutations. Item
6
+ # types are inferred from the dataset's JSON schema when present.
7
+ class GraphqlSchema
8
+ # Permissive scalar carrying any JSON value.
9
+ class JsonScalar < GraphQL::Schema::Scalar
10
+ def self.coerce_input: (untyped input_value, untyped _context) -> untyped
11
+
12
+ def self.coerce_result: (untyped ruby_value, untyped _context) -> untyped
13
+ end
14
+
15
+ def initialize: (Mount mount, GraphqlApi api) -> void
16
+
17
+ def build: () -> singleton(GraphQL::Schema)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ module Capsium
2
+ class Reactor
3
+ # Apache htpasswd verification (05x-authentication). Supported hash
4
+ # formats: bcrypt ($2a$/$2b$/$2y$), Apache apr1 MD5 and md5-crypt
5
+ # ($apr1$/$1$), unsalted SHA-1 ({SHA}), and a crypt(3) fallback for
6
+ # the rest (platform-dependent).
7
+ class Htpasswd
8
+ # md5-crypt as deployed by Apache htpasswd, OpenSSL and glibc.
9
+ module Md5Crypt
10
+ ITOA64: String
11
+ DIGEST_GROUPS: Array[Array[Integer]]
12
+
13
+ def self.verify: (String hash, String password) -> bool
14
+
15
+ def self.digest: (String password, String salt, String magic) -> String
16
+
17
+ def self.initial_digest: (String password, String salt, String magic) -> String
18
+
19
+ def self.stretch_digest: (Integer iteration, String password, String salt, String final) -> String
20
+
21
+ def self.to64: (String final) -> String
22
+ end
23
+
24
+ attr_reader path: String
25
+
26
+ def initialize: (String path) -> void
27
+
28
+ def usernames: () -> Array[String]
29
+
30
+ def verify?: (String username, String password) -> bool
31
+ end
32
+
33
+ # Constant-time string comparison (length-check first).
34
+ def self.secure_compare: (String own, String theirs) -> bool
35
+ end
36
+ end
@@ -1,23 +1,36 @@
1
1
  module Capsium
2
2
  class Reactor
3
- # Monitoring HTTP API reports for the package this reactor serves
4
- # (ARCHITECTURE.md section 7).
3
+ # Monitoring HTTP API reports (ARCHITECTURE.md section 7):
4
+ # /api/v1/introspect/* aggregates every mounted package, plus
5
+ # reactor-level /introspect/* and per-package /package/:id/*
6
+ # endpoints resolved by package name.
5
7
  class Introspection
6
8
  METADATA_PATH: String
7
9
  ROUTES_PATH: String
8
10
  CONTENT_HASHES_PATH: String
9
11
  CONTENT_VALIDITY_PATH: String
10
12
  PATHS: Array[String]
13
+ STATUS_PATH: String
14
+ CONFIG_PATH: String
15
+ METRICS_PATH: String
16
+ REACTOR_PATHS: Array[String]
17
+ PACKAGE_MOUNT: String
18
+ PACKAGE_PATH_PATTERN: Regexp
19
+ DEFAULT_LOG_LINES: Integer
20
+ MAX_LOG_LINES: Integer
11
21
 
12
- attr_reader package: Package
22
+ attr_reader packages: Array[Package]
13
23
 
14
- def initialize: (Package package) -> void
24
+ def initialize: (Array[Package] packages, ?reactor: Reactor? reactor) -> void
25
+
26
+ # The first mounted package (the single-package view).
27
+ def package: () -> Package
15
28
 
16
29
  def endpoint?: (String path) -> bool
17
30
 
18
- # The report body for an introspection endpoint, or nil when the
19
- # path is not an introspection endpoint.
20
- def report_for: (String path) -> Hash[Symbol, untyped]?
31
+ # The report body for an endpoint, or nil when the path is not an
32
+ # endpoint or the package name does not match (404).
33
+ def report_for: (String path, ?params: Hash[String, String] params) -> Hash[Symbol, untyped]?
21
34
 
22
35
  def metadata_report: () -> Hash[Symbol, untyped]
23
36
 
@@ -26,6 +39,13 @@ module Capsium
26
39
  def content_hashes_report: () -> Hash[Symbol, untyped]
27
40
 
28
41
  def content_validity_report: () -> Hash[Symbol, untyped]
42
+
43
+ def status_report: () -> Hash[Symbol, untyped]
44
+
45
+ # Reactor configuration; secrets are never exposed.
46
+ def config_report: () -> Hash[Symbol, untyped]
47
+
48
+ def metrics_report: () -> Hash[Symbol, untyped]
29
49
  end
30
50
  end
31
51
  end
@@ -0,0 +1,13 @@
1
+ module Capsium
2
+ class Reactor
3
+ # Minimal in-memory request counters for the /introspect/metrics
4
+ # endpoint, thread-safe via Mutex.
5
+ class Metrics
6
+ def initialize: () -> void
7
+
8
+ def record: (Integer status) -> Metrics
9
+
10
+ def snapshot: () -> Hash[Symbol, untyped]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,83 @@
1
+ module Capsium
2
+ class Reactor
3
+ # Raised when two mounts claim the same URL prefix, whether given
4
+ # explicitly or derived from package names.
5
+ class MountConflictError < Capsium::Error
6
+ end
7
+
8
+ # One mounted package: the URL prefix it answers under plus its
9
+ # serving state. The reactor serves a list of mounts resolved by
10
+ # longest-prefix matching; default mount points are "/" for the
11
+ # first source and "/<metadata.name>/" for each additional one.
12
+ class Mount
13
+ ROOT_PATH: String
14
+ SPEC_SEPARATOR: String
15
+
16
+ # A mount entry as accepted from the CLI or a JSON config file:
17
+ # "path" may be nil (the default is assigned on build), "source"
18
+ # is a package directory, .cap file or Package, "store"
19
+ # optionally overrides the global package store for this mount.
20
+ class Entry
21
+ def self.new: (path: String? path, source: (Package | String) source, store: (Package::Store | String)? store) -> void
22
+
23
+ def path: () -> String?
24
+
25
+ def source: () -> (Package | String)
26
+
27
+ def store: () -> (Package::Store | String)?
28
+
29
+ def with: (?path: String? path, ?source: (Package | String) source, ?store: (Package::Store | String)? store) -> Entry
30
+ end
31
+
32
+ attr_reader path: String
33
+ attr_reader package: Package
34
+ attr_reader overlay: Overlay?
35
+
36
+ def self.parse_spec: (String spec) -> Entry
37
+
38
+ def self.config_entries: (String config_file) -> Array[Entry]
39
+
40
+ def self.build: (Array[Entry] entries, ?store: (Package::Store | String)? store, ?registry: (Capsium::Registry | String)? registry) -> Array[Mount]
41
+
42
+ def self.load_package: ((Package | String) source, (Package::Store | String)? store, (Capsium::Registry | String)? registry) -> Package
43
+
44
+ def self.normalize_path: (String? path) -> String
45
+
46
+ def initialize: (path: String? path, package: (Package | String) package, ?store: (Package::Store | String)? store, ?registry: (Capsium::Registry | String)? registry, ?workdir: String? workdir) -> void
47
+
48
+ # Attaches the reactor workdir (creates the writable overlay).
49
+ def attach_workdir: (String workdir) -> void
50
+
51
+ # Writable unless the package declares "readOnly": true.
52
+ def writable?: () -> bool
53
+
54
+ def matches?: (String request_path) -> bool
55
+
56
+ def inner_path: (String request_path) -> String
57
+
58
+ def routes: () -> Package::Routes
59
+
60
+ def merged_view: () -> Package::MergedView
61
+
62
+ # The dataset as served: base data merged with overlay mutations.
63
+ def dataset_data: (Package::Dataset dataset) -> untyped
64
+
65
+ def data_api: () -> DataApi
66
+
67
+ def content_api: () -> ContentApi
68
+
69
+ # Whether this mount exposes a GraphQL API (any file-backed dataset).
70
+ def graphql?: () -> bool
71
+
72
+ def graphql_api: () -> GraphqlApi
73
+
74
+ # The one-line "name version" summary used in reactor logs.
75
+ def summary: () -> String
76
+
77
+ # The WEBrick mount points serving this mount.
78
+ def server_paths: () -> Array[String]
79
+
80
+ def reload: () -> Package
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,31 @@
1
+ module Capsium
2
+ class Reactor
3
+ # OAuth2 authorization-code flow client (05x-authentication), used by
4
+ # the Authenticator. Talks to the provider over net/http; the client
5
+ # secret never comes from the package.
6
+ class OAuth2
7
+ # The token exchange or userinfo fetch failed at the provider.
8
+ class FlowError < Capsium::Error
9
+ end
10
+
11
+ GRANT_TYPE: String
12
+
13
+ attr_reader config: Package::OAuth2Config
14
+
15
+ def initialize: (Package::OAuth2Config config, client_secret: String client_secret,
16
+ session: Session session, ?base_url: String? base_url) -> void
17
+
18
+ # The provider URL the login endpoint redirects the browser to.
19
+ def authorization_url: (untyped request) -> String
20
+
21
+ def valid_state?: (String? state) -> bool
22
+
23
+ # Exchanges the authorization code and fetches the userinfo
24
+ # claims. Returns the raw userinfo hash; raises FlowError on
25
+ # provider errors.
26
+ def complete: (String? code, untyped request) -> Hash[String, untyped]
27
+
28
+ def callback_uri: (untyped request) -> String
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,72 @@
1
+ module Capsium
2
+ class Reactor
3
+ # The writable top overlay layer of a mounted package
4
+ # (ARCHITECTURE.md section 5a): an append-only record of writes in
5
+ # the reactor workdir (content files, tombstones, per-dataset
6
+ # mutation logs). The immutable package never changes on disk.
7
+ class Overlay
8
+ # Base class for the typed overlay errors the APIs map to HTTP
9
+ # statuses.
10
+ class Error < Capsium::Error
11
+ end
12
+
13
+ # The addressed item does not exist in the merged collection (404).
14
+ class ItemNotFoundError < Error
15
+ end
16
+
17
+ # The write conflicts with existing state (409).
18
+ class ConflictError < Error
19
+ end
20
+
21
+ # The dataset cannot take item writes or the path is unsafe.
22
+ class UnsupportedDatasetError < Error
23
+ end
24
+
25
+ # The candidate document violates the dataset's JSON schema (422).
26
+ class SchemaViolationError < Error
27
+ attr_reader messages: Array[String]
28
+
29
+ def initialize: (Array[String] messages) -> void
30
+ end
31
+
32
+ TOMBSTONE_FILE: String
33
+ SAFE_NAME_PATTERN: Regexp
34
+
35
+ attr_reader root: String
36
+ attr_reader tombstones: Set[String]
37
+
38
+ def initialize: (root: String root) -> void
39
+
40
+ def content_root: () -> String
41
+
42
+ def data_root: () -> String
43
+
44
+ # The overlay as the topmost MergedView layer.
45
+ def layer: () -> Package::MergedView::Layer
46
+
47
+ def write_content: (String content_relative, String body) -> void
48
+
49
+ def delete_content: (String content_relative) -> void
50
+
51
+ # The dataset as served: base data merged with the mutation log.
52
+ def dataset_data: (Package::Dataset dataset) -> untyped
53
+
54
+ # The merged item collection.
55
+ def items: (Package::Dataset dataset) -> Array[untyped]
56
+
57
+ def item: (Package::Dataset dataset, String id) -> untyped
58
+
59
+ def append_item: (Package::Dataset dataset, untyped item) -> String
60
+
61
+ def replace_item: (Package::Dataset dataset, String id, untyped item) -> void
62
+
63
+ def delete_item: (Package::Dataset dataset, String id) -> nil
64
+
65
+ # Whether the dataset has recorded mutations.
66
+ def mutations?: (String name) -> bool
67
+
68
+ # The item id convention: an "id" field, else nil (positional).
69
+ def item_id: (untyped item) -> String?
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,12 @@
1
+ module Capsium
2
+ class Reactor
3
+ # Folds a mounted package's base plus its overlay into a NEW
4
+ # versioned .cap (name-version+patch.cap) in the reactor workdir.
5
+ class PackageSaver
6
+ def initialize: (Mount mount) -> void
7
+
8
+ # Saves and returns the {"path", "sha256", "name", "version"} hash.
9
+ def save: (String workdir) -> Hash[String, String]
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ module Capsium
2
+ class Reactor
3
+ # Plain HTTP response writers shared by the reactor dispatch, the
4
+ # route serving and the API handlers (data, GraphQL, save).
5
+ module Responses
6
+ private
7
+
8
+ def respond_not_found: (untyped response) -> untyped
9
+
10
+ def respond_forbidden: (untyped response) -> untyped
11
+
12
+ def respond_not_implemented: (untyped response) -> untyped
13
+
14
+ def respond_method_not_allowed: (untyped response) -> untyped
15
+
16
+ def respond_text: (untyped response, Integer status, String body) -> untyped
17
+
18
+ def respond_json: (untyped response, Integer status, untyped body) -> untyped
19
+
20
+ def respond_error: (untyped response, Integer status, String message, ?messages: Array[String]? messages) -> untyped
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,46 @@
1
+ module Capsium
2
+ class Reactor
3
+ # Serving of resolved routes (static files through the merged view,
4
+ # datasets, handler-route 501s), mixed into Reactor. Includes route
5
+ # inheritance processing per 05x-routing.
6
+ module Serving
7
+ include Responses
8
+
9
+ private
10
+
11
+ # One request metric and one log line per served request.
12
+ def record_request: (untyped request, untyped response) -> untyped
13
+
14
+ # The mount answering a request path: longest matching prefix wins.
15
+ def resolve_mount: (String path) -> Mount?
16
+
17
+ def mounts_by_length: () -> Array[Mount]
18
+
19
+ # The root ("/") mount, or the first mount when nothing is mounted
20
+ # at the root.
21
+ def root_mount: () -> Mount
22
+
23
+ # Serves a request that matched a mount.
24
+ def serve_mounted_request: (Mount mount, untyped identity, untyped request, untyped response) -> untyped
25
+
26
+ def serve_data_api: (Mount mount, untyped identity, String inner, untyped request, untyped response) -> untyped
27
+
28
+ def serve_content_api: (Mount mount, untyped identity, String inner, untyped request, untyped response) -> untyped
29
+
30
+ # Route-level access control; false when the response was written.
31
+ def authorized?: (untyped identity, Package::Route route, untyped response) -> bool
32
+
33
+ def serve_route: (Mount mount, Package::Route route, untyped response) -> untyped
34
+
35
+ def serve_file: (Mount mount, Package::Route route, untyped response) -> untyped
36
+
37
+ def inherited_processing: (Package::Route route, String body, Hash[String, String] headers) -> [String, Hash[String, String]]
38
+
39
+ def content_type_for: (Mount mount, Package::Route route, String content_path) -> String
40
+
41
+ def headers_for: (Package::Route route) -> Hash[String, String]
42
+
43
+ def serve_dataset: (Mount mount, String dataset_name, untyped response) -> untyped
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,36 @@
1
+ module Capsium
2
+ class Reactor
3
+ # HMAC-SHA256 signed session cookies (05x-authentication). The
4
+ # signing secret comes from deploy.json (sessionSecret) or is
5
+ # generated and persisted reactor-side (never in the package).
6
+ class Session
7
+ COOKIE_NAME: String
8
+ SECRET_BYTES: Integer
9
+
10
+ def self.hmac: (String secret, String data) -> String
11
+
12
+ # The persisted reactor-side secret: loaded from state_file when
13
+ # present, generated and written (mode 0600) otherwise.
14
+ def self.load_or_generate_secret: (String? state_file) -> String
15
+
16
+ attr_reader secret: String
17
+
18
+ def initialize: (?secret: String? secret, ?state_file: String? state_file) -> void
19
+
20
+ # HMAC-SHA256 of data with the session secret (hex).
21
+ def sign: (String data) -> String
22
+
23
+ def encode: (Hash[String, untyped] payload) -> String
24
+
25
+ # The verified payload, or nil when the cookie is missing or the
26
+ # signature does not match.
27
+ def decode: (String? cookie_value) -> Hash[String, untyped]?
28
+
29
+ # The identity for a request's Cookie header, or nil.
30
+ def identity_from: (untyped request) -> Hash[String, untyped]?
31
+
32
+ # A Set-Cookie value establishing the identity session.
33
+ def cookie_for: (Hash[String, untyped] identity) -> String
34
+ end
35
+ end
36
+ end
@@ -3,8 +3,13 @@ module Capsium
3
3
  # Monitoring HTTP API (ARCHITECTURE.md section 7) and reloads the
4
4
  # package on filesystem changes.
5
5
  class Reactor
6
+ include Responses
7
+ include Endpoints
8
+ include Serving
9
+
6
10
  DEFAULT_PORT: Integer
7
11
  DEFAULT_CACHE_CONTROL: String
12
+ PACKAGE_SAVE_PATTERN: Regexp
8
13
 
9
14
  attr_reader package: Package
10
15
  attr_reader package_path: String
@@ -14,10 +19,23 @@ module Capsium
14
19
  attr_reader server: untyped
15
20
  attr_reader server_thread: Thread?
16
21
  attr_reader introspection: Introspection
22
+ attr_reader authenticator: Authenticator
23
+ attr_reader store: (Package::Store | String)?
24
+ attr_reader registry: (Capsium::Registry | String)?
25
+ attr_reader started_at: Time
26
+ attr_reader metrics: Metrics
27
+ attr_reader log_buffer: Capsium::LogBuffer
28
+ attr_reader mounts: Array[Mount]
29
+ attr_reader workdir: String
17
30
 
18
- def initialize: (package: Package | String package, ?port: Integer port,
31
+ def initialize: (?package: (Package | String)? package, ?mounts: Array[Mount]? mounts,
32
+ ?port: Integer port,
19
33
  ?cache_control: String? cache_control,
20
- ?do_not_listen: bool do_not_listen) -> void
34
+ ?do_not_listen: bool do_not_listen,
35
+ ?store: (Package::Store | String)? store,
36
+ ?deploy: (Hash[String, untyped] | String)? deploy,
37
+ ?registry: (Capsium::Registry | String)? registry,
38
+ ?workdir: String? workdir) -> void
21
39
 
22
40
  def serve: () -> untyped
23
41
 
@@ -26,5 +44,7 @@ module Capsium
26
44
  def mount_routes: () -> void
27
45
 
28
46
  def restart_server: () -> Thread
47
+
48
+ def cleanup: () -> void
29
49
  end
30
50
  end
@@ -0,0 +1,16 @@
1
+ module Capsium
2
+ class Registry
3
+ # A static registry in a local directory (read-write).
4
+ class Local < Registry
5
+ attr_reader dir: String
6
+
7
+ def initialize: (String dir) -> void
8
+
9
+ def location: () -> String
10
+
11
+ # Validates cap_path, copies it into the registry and records it
12
+ # in index.json. Returns the recorded Entry.
13
+ def push: (String cap_path) -> Entry
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ module Capsium
2
+ class Registry
3
+ # A read-only static registry behind an https base URL (plain http
4
+ # for loopback hosts only).
5
+ class Remote < Registry
6
+ OPEN_TIMEOUT: Integer
7
+ READ_TIMEOUT: Integer
8
+ MAX_REDIRECTS: Integer
9
+ LOOPBACK_NETWORKS: Array[untyped]
10
+
11
+ attr_reader base_url: String
12
+
13
+ def initialize: (String base_url) -> void
14
+
15
+ def location: () -> String
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,83 @@
1
+ module Capsium
2
+ # A static package registry (directory or https base URL with an
3
+ # index.json): Capsium::Registry::Local (read-write) and
4
+ # Capsium::Registry::Remote (read-only). See lib/capsium/registry.rb.
5
+ class Registry
6
+ INDEX_FILE: String
7
+ ENV_VAR: String
8
+
9
+ # Base error for every registry failure.
10
+ class RegistryError < Capsium::Error
11
+ end
12
+
13
+ # No registry reference was given (neither flag nor CAPSIUM_REGISTRY).
14
+ class RegistryNotConfiguredError < RegistryError
15
+ end
16
+
17
+ # The registry itself is unusable (bad path or URL, unreadable index).
18
+ class InvalidRegistryError < RegistryError
19
+ end
20
+
21
+ # A .cap offered to push failed package validation.
22
+ class InvalidPackageError < RegistryError
23
+ end
24
+
25
+ # The index has no entry for the requested package GUID.
26
+ class PackageNotFoundError < RegistryError
27
+ end
28
+
29
+ # The registry has the GUID but no version satisfies the constraint.
30
+ class UnsatisfiableConstraintError < RegistryError
31
+ end
32
+
33
+ # A fetched .cap does not match the sha256 declared in the index.
34
+ class ChecksumMismatchError < RegistryError
35
+ end
36
+
37
+ # A registry file could not be retrieved (network or HTTP failure).
38
+ class FetchError < RegistryError
39
+ end
40
+
41
+ # One indexed version of a registry package.
42
+ class Entry
43
+ def self.new: (guid: String, name: String, version: Package::Version,
44
+ file: String, sha256: String, size: Integer) -> void
45
+
46
+ def guid: () -> String
47
+ def name: () -> String
48
+ def version: () -> Package::Version
49
+ def file: () -> String
50
+ def sha256: () -> String
51
+ def size: () -> Integer
52
+
53
+ # The canonical store file name for this entry's .cap.
54
+ def cap_file_name: () -> String
55
+ end
56
+
57
+ # The registry at the given reference: a Local directory or a Remote
58
+ # http(s) base URL. Raises RegistryNotConfiguredError when no
59
+ # reference is given.
60
+ def self.fetch: (String? ref) -> Registry
61
+
62
+ # The registry named by the CAPSIUM_REGISTRY environment variable,
63
+ # or nil when unset.
64
+ def self.default: () -> Registry?
65
+
66
+ def initialize: () -> void
67
+
68
+ # Where this registry lives (directory path or base URL).
69
+ def location: () -> String
70
+
71
+ # The newest indexed version of the package GUID satisfying the
72
+ # semver constraint.
73
+ def resolve: (String guid, ?String constraint) -> Entry
74
+
75
+ # Resolves, fetches, checksum-verifies and installs the newest
76
+ # satisfying .cap into the package store, returning the store path.
77
+ def install: (String guid, ?String constraint,
78
+ store: (Package::Store | String) store) -> String
79
+
80
+ # Only Local registries are writable.
81
+ def push: (String cap_path) -> Entry
82
+ end
83
+ end
data/sig/graphql.rbs ADDED
@@ -0,0 +1,12 @@
1
+ # Minimal RBS stubs for the graphql gem: only the constants capsium's
2
+ # own signatures reference (the gem ships full sigs, but this project
3
+ # does not use an RBS collection).
4
+ module GraphQL
5
+ class Schema
6
+ def self.execute: (String query, ?variables: Hash[String, untyped] variables, ?operation_name: String? operation_name) -> untyped
7
+
8
+ class Scalar
9
+ def self.graphql_name: (?String name) -> void
10
+ end
11
+ end
12
+ end