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,66 @@
1
+ module Capsium
2
+ class Package
3
+ # Signs and verifies Capsium packages with RSA-SHA256 digital
4
+ # signatures (05x-packaging "Digital Signature Using X.509",
5
+ # 05x-security "Digital Signatures"). The signed payload is the
6
+ # concatenation, in sorted package-relative path order, of the raw
7
+ # bytes of every file covered by the security.json integrity
8
+ # checksums (everything except security.json and signature.sig).
9
+ class Signer
10
+ ALGORITHM: String
11
+ MIN_KEY_BITS: Integer
12
+
13
+ # Package-relative name of the embedded public key PEM recorded in
14
+ # security.json digitalSignatures.publicKey.
15
+ PUBLIC_KEY_FILE: String
16
+
17
+ # Structural problems: no signature declared, missing signature or
18
+ # key files, unloadable keys, checksum-covered files missing.
19
+ class SignatureError < Capsium::Error
20
+ end
21
+
22
+ # A signature is declared but does not match the package contents.
23
+ class SignatureMismatchError < SignatureError
24
+ end
25
+
26
+ # Signature operations requested on an unsigned package.
27
+ class UnsignedPackageError < SignatureError
28
+ end
29
+
30
+ attr_reader package_path: String
31
+
32
+ def initialize: (String package_path) -> void
33
+
34
+ # Signs a package directory in place, or a .cap file (unpacked,
35
+ # signed, recompressed). Returns the signed path.
36
+ def self.sign_package: (String path, String private_key_path, ?String? certificate_path) -> String
37
+
38
+ # Verifies the declared signature of a package directory or .cap
39
+ # file (false on mismatch; raises typed SignatureError subclasses
40
+ # on structural problems, e.g. an unsigned package). The signer is
41
+ # selected from the declared digitalSignatures certificateType:
42
+ # OpenPGP packages verify through OpenPgpSigner.
43
+ def self.verify_package: (String path, ?String? public_key_path) -> bool
44
+
45
+ # The signer class matching a package's declared signature scheme:
46
+ # OpenPgpSigner when security.json declares
47
+ # digitalSignatures.certificateType "OpenPGP", Signer otherwise.
48
+ def self.signer_class_for: (String package_path) -> (singleton(Signer) | singleton(OpenPgpSigner))
49
+
50
+ # Signs the package directory in place: embeds the public key PEM,
51
+ # regenerates security.json (checksums plus digitalSignatures) and
52
+ # writes the raw RSA-SHA256 signature to signature.sig. Returns the
53
+ # path of the written signature file.
54
+ def sign: (String private_key_path, ?String? certificate_path) -> String
55
+
56
+ # True when the declared signature verifies against the payload;
57
+ # false on mismatch. Raises typed SignatureError subclasses on
58
+ # structural problems.
59
+ def verify: (?String? public_key_path) -> bool
60
+
61
+ def verify!: (?String? public_key_path) -> bool
62
+
63
+ def signed?: () -> bool
64
+ end
65
+ end
66
+ end
@@ -1,5 +1,23 @@
1
1
  module Capsium
2
2
  class Package
3
+ # A single storage layer entry (ARCHITECTURE.md section 5a): a
4
+ # package-relative directory mirroring the content/ tree. Layers are
5
+ # stacked bottom -> top in declaration order; "visibility: private"
6
+ # layers are hidden from dependent packages.
7
+ class LayerConfig
8
+ attr_accessor path: String?
9
+ attr_accessor writable: bool
10
+ attr_accessor visibility: String
11
+
12
+ def initialize: (?path: String? path, ?writable: bool writable,
13
+ ?visibility: String? visibility) -> void
14
+
15
+ def private?: () -> bool
16
+
17
+ def to_json: (*untyped args) -> String
18
+ def to_hash: () -> Hash[String, untyped]
19
+ end
20
+
3
21
  # A single dataset entry (ARCHITECTURE.md section 5): schema-backed
4
22
  # file (via "source") or SQLite ("databaseFile" + "table").
5
23
  class DatasetConfig
@@ -29,11 +47,13 @@ module Capsium
29
47
  def to_hash: () -> Hash[String, untyped]
30
48
  end
31
49
 
32
- # The "storage" object holding the dataSets map.
50
+ # The "storage" object holding the dataSets map and the layers stack.
33
51
  class StorageData
34
52
  attr_accessor data_sets: Hash[String, DatasetConfig]
53
+ attr_accessor layers: Array[LayerConfig]
35
54
 
36
- def initialize: (?data_sets: Hash[String, DatasetConfig] data_sets) -> void
55
+ def initialize: (?data_sets: Hash[String, DatasetConfig] data_sets,
56
+ ?layers: Array[LayerConfig] layers) -> void
37
57
 
38
58
  def data_sets_from_json: (StorageData model, untyped value) -> void
39
59
 
@@ -54,6 +74,10 @@ module Capsium
54
74
 
55
75
  def data_sets: () -> Hash[String, DatasetConfig]
56
76
 
77
+ # The configured storage layers, bottom -> top (empty when the
78
+ # package uses the single implicit content/ layer).
79
+ def layers: () -> Array[LayerConfig]
80
+
57
81
  def to_json: (*untyped args) -> String
58
82
  def to_hash: () -> Hash[String, untyped]
59
83
  end
@@ -0,0 +1,43 @@
1
+ module Capsium
2
+ class Package
3
+ # A package store directory (ARCHITECTURE.md section 4a):
4
+ # "<name>-<version>.cap" files plus an optional index.json mapping
5
+ # dependency GUID -> file name. Dependencies resolve to the newest
6
+ # version satisfying their semver range.
7
+ class Store
8
+ INDEX_FILE: String
9
+ CAP_GLOB: String
10
+
11
+ # One store .cap with its identity per its metadata.json.
12
+ class CatalogEntry
13
+ def self.new: (String path, String? guid, Version version) -> void
14
+
15
+ def path: () -> String
16
+ def guid: () -> String?
17
+ def version: () -> Version
18
+ end
19
+
20
+ attr_reader dir: String
21
+
22
+ # The store configured via the CAPSIUM_STORE environment variable,
23
+ # or nil when unset.
24
+ def self.default: () -> Store?
25
+
26
+ def initialize: (String dir) -> void
27
+
28
+ # The newest .cap providing the dependency GUID whose version
29
+ # satisfies the range string. Raises DependencyNotFoundError or
30
+ # UnsatisfiableDependencyError when it cannot.
31
+ def find: (String guid, String range_string) -> String
32
+
33
+ # Every .cap in the store with its metadata identity (memoized).
34
+ def catalog: () -> Array[CatalogEntry]
35
+
36
+ # Installs the .cap at source_path into the store as file_name,
37
+ # records guid -> file_name in index.json (atomically) and returns
38
+ # the installed path.
39
+ def install: (String source_path, guid: String guid,
40
+ file_name: String file_name) -> String
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,21 @@
1
+ module Capsium
2
+ class Package
3
+ module Testing
4
+ # A "config" test (05x-testing): the configuration file must exist
5
+ # in the package and parse as the declared format; known Capsium
6
+ # package configs are additionally validated against their
7
+ # canonical models.
8
+ class ConfigTest < TestCase
9
+ FORMATS: Array[String]
10
+
11
+ attr_reader format: String
12
+ attr_reader config_file: String
13
+
14
+ def initialize: (name: String name, format: String format,
15
+ config_file: String config_file) -> void
16
+
17
+ def run: (Context context) -> TestCase::Result
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module Capsium
2
+ class Package
3
+ module Testing
4
+ # What a test run needs: the extracted package directory and, when
5
+ # route tests run, the base URL of the reactor serving the package.
6
+ class Context
7
+ attr_reader package_path: String
8
+ attr_reader base_url: String?
9
+
10
+ def initialize: (package_path: String package_path, base_url: String? base_url) -> void
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,22 @@
1
+ module Capsium
2
+ class Package
3
+ module Testing
4
+ # A "data_validation" test (05x-testing): the rows of the data
5
+ # file (format: json or yaml) must validate against the JSON
6
+ # schema file.
7
+ class DataValidationTest < TestCase
8
+ FORMATS: Array[String]
9
+
10
+ attr_reader format: String
11
+ attr_reader data_file: String
12
+ attr_reader schema_file: String
13
+
14
+ def initialize: (name: String name, format: String format,
15
+ data_file: String data_file,
16
+ schema_file: String schema_file) -> void
17
+
18
+ def run: (Context context) -> TestCase::Result
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module Capsium
2
+ class Package
3
+ module Testing
4
+ # A "file" test (05x-testing): the file must exist in the package;
5
+ # optionally its content must contain a string.
6
+ class FileTest < TestCase
7
+ attr_reader path: String
8
+ attr_reader contains: String?
9
+
10
+ def initialize: (name: String name, path: String path,
11
+ ?contains: String? contains) -> void
12
+
13
+ def run: (Context context) -> TestCase::Result
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ module Capsium
2
+ class Package
3
+ module Testing
4
+ # The outcome of a test suite run: one Result per test case.
5
+ class Report
6
+ attr_reader results: Array[TestCase::Result]
7
+
8
+ def initialize: (?Array[TestCase::Result] results) -> void
9
+
10
+ def <<: (TestCase::Result result) -> Report
11
+
12
+ def ok?: () -> bool
13
+
14
+ def failures: () -> Array[TestCase::Result]
15
+
16
+ def summary: () -> String
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ module Capsium
2
+ class Package
3
+ module Testing
4
+ # A "route" test (05x-testing): requests the URL path from the
5
+ # reactor serving the package and checks the expected status, and
6
+ # optionally the content type and a response body substring.
7
+ class RouteTest < TestCase
8
+ attr_reader url: String
9
+ attr_reader expected_status: Integer
10
+ attr_reader response_contains: String?
11
+ attr_reader expected_content_type: String?
12
+
13
+ def initialize: (name: String name, url: String url,
14
+ expected_status: Integer expected_status,
15
+ ?response_contains: String? response_contains,
16
+ ?expected_content_type: String? expected_content_type) -> void
17
+
18
+ def run: (Context context) -> TestCase::Result
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,41 @@
1
+ module Capsium
2
+ class Package
3
+ module Testing
4
+ # Base class for one test of the Capsium testing YAML DSL
5
+ # (05x-testing). Subclasses implement a test kind and register
6
+ # themselves under their DSL type name (open/closed registry).
7
+ class TestCase
8
+ # The outcome of running one test.
9
+ class Result
10
+ attr_reader name: String
11
+ attr_reader ok: bool
12
+ attr_reader messages: Array[String]
13
+
14
+ def initialize: (name: String name, ok: bool ok, messages: Array[String] messages) -> void
15
+
16
+ def ok?: () -> bool
17
+ end
18
+
19
+ # Raised for invalid test definitions.
20
+ class DefinitionError < Capsium::Error
21
+ end
22
+
23
+ attr_reader name: String
24
+
25
+ def initialize: (name: String name) -> void
26
+
27
+ # Runs the test against the context and returns a Result.
28
+ def run: (Context context) -> Result
29
+
30
+ def self.register: (String type, singleton(TestCase) klass) -> void
31
+
32
+ def self.types: () -> Hash[String, singleton(TestCase)]
33
+
34
+ # Builds a test case from a YAML definition hash (string keys).
35
+ def self.build: (untyped definition) -> TestCase
36
+
37
+ def self.from_h: (Hash[String, untyped] definition) -> TestCase
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,20 @@
1
+ module Capsium
2
+ class Package
3
+ module Testing
4
+ # Loads and runs the package's tests/*.yaml suites (05x-testing).
5
+ # Route tests are served by a reactor on an ephemeral port for the
6
+ # duration of the run.
7
+ class TestSuite
8
+ TESTS_DIR: String
9
+
10
+ attr_reader package: Capsium::Package
11
+
12
+ def initialize: (Capsium::Package package) -> void
13
+
14
+ def test_files: () -> Array[String]
15
+
16
+ def run: () -> Report
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ module Capsium
2
+ class Package
3
+ # Runner for the Capsium package testing YAML DSL (05x-testing).
4
+ module Testing
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,26 @@
1
+ module Capsium
2
+ class Package
3
+ # Integrity and digital-signature verification of a loaded package
4
+ # (ARCHITECTURE.md section 6), mixed into Package.
5
+ module Verification
6
+ # Verifies the package against security.json. Returns a list of
7
+ # typed errors; empty when no security.json is present or all
8
+ # checksums match.
9
+ def verify_integrity: () -> Array[Security::ChecksumMismatch | Security::MissingFile | Security::UncheckedFile]
10
+
11
+ def verify_integrity!: () -> void
12
+
13
+ # Whether security.json declares a digital signature for this package.
14
+ def signed?: () -> bool
15
+
16
+ # Verifies the declared digital signature against the
17
+ # checksum-covered payload, through the signer matching the
18
+ # declared certificateType (OpenPgpSigner for OpenPGP, the
19
+ # RSA-SHA256 Signer otherwise). True when the package is unsigned
20
+ # or the signature verifies; false on mismatch.
21
+ def verify_signature: () -> bool
22
+
23
+ def verify_signature!: () -> void
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,39 @@
1
+ module Capsium
2
+ class Package
3
+ # A semantic version (MAJOR.MINOR.PATCH[-prerelease][+build]) with
4
+ # semver precedence ordering.
5
+ class Version
6
+ include Comparable
7
+
8
+ PATTERN: Regexp
9
+
10
+ attr_reader major: Integer
11
+ attr_reader minor: Integer
12
+ attr_reader patch: Integer
13
+ attr_reader prerelease: String?
14
+
15
+ def self.parse: (String string) -> Version
16
+
17
+ def initialize: (Integer major, Integer minor, Integer patch,
18
+ ?String? prerelease) -> void
19
+
20
+ def <=>: (Version other) -> Integer
21
+
22
+ def to_s: () -> String
23
+ end
24
+
25
+ # A semver range for metadata.dependencies (ARCHITECTURE.md section
26
+ # 4a): "*", exact versions, wildcards/partials, caret, tilde,
27
+ # comparison operators and comma/space conjunctions.
28
+ class VersionRange
29
+ NUMERIC_PART: Regexp
30
+ WILDCARD_PART: Regexp
31
+
32
+ def self.parse: (String string) -> VersionRange
33
+
34
+ def initialize: (Array[[String, Version]] bounds) -> void
35
+
36
+ def satisfied_by?: (String | Version version) -> bool
37
+ end
38
+ end
39
+ end
@@ -2,11 +2,17 @@ module Capsium
2
2
  # A Capsium package loaded from a directory or a .cap file
3
3
  # (ARCHITECTURE.md section 1).
4
4
  class Package
5
+ include Verification
6
+ include Composition
7
+ include Preparation
8
+
5
9
  MANIFEST_FILE: String
6
10
  METADATA_FILE: String
7
11
  STORAGE_FILE: String
8
12
  ROUTES_FILE: String
9
13
  SECURITY_FILE: String
14
+ SIGNATURE_FILE: String
15
+ AUTHENTICATION_FILE: String
10
16
  CONTENT_DIR: String
11
17
  DATA_DIR: String
12
18
 
@@ -18,36 +24,43 @@ module Capsium
18
24
  attr_reader storage: Storage
19
25
  attr_reader security: Security
20
26
  attr_reader load_type: Symbol
27
+ attr_reader resolved_dependencies: Array[ResolvedDependency]
28
+ attr_reader authentication: Authentication
21
29
 
22
- def initialize: (String | Pathname path, ?load_type: Symbol? load_type) -> void
23
-
24
- def prepare_package: (Pathname path) -> (String | Pathname)
30
+ # Loads a package directory or .cap file. Composite packages
31
+ # (metadata.dependencies, ARCHITECTURE.md section 4a) resolve against
32
+ # the package store given as `store` (directory path or Store) or via
33
+ # CAPSIUM_STORE, falling back to the registry given as `registry`
34
+ # or via CAPSIUM_REGISTRY when the store has no package for a
35
+ # dependency GUID. `dependency_chain` is internal: the ancestor GUIDs
36
+ # used for circular-dependency detection during recursive resolution.
37
+ def initialize: (String | Pathname path, ?load_type: Symbol? load_type,
38
+ ?decryption_key: String? decryption_key,
39
+ ?store: (Store | String)? store,
40
+ ?registry: (Capsium::Registry | String)? registry,
41
+ ?dependency_chain: Array[String] dependency_chain,
42
+ ?bundle: Bundle? bundle) -> void
25
43
 
26
44
  def solidify: () -> void
27
45
 
28
- def decompress_cap_file: (String | Pathname file_path) -> String
29
-
30
46
  def load_package: () -> void
31
47
 
32
- # Removes the temporary extraction directory of a .cap-loaded
33
- # package; a no-op for directory loads.
48
+ # Removes the temporary extraction directories of the package and its
49
+ # resolved dependencies; a no-op for directory loads.
34
50
  def cleanup: () -> void
35
51
 
36
52
  def datasets: () -> Array[Dataset]
37
53
 
54
+ # The merged content view across the storage layers (ARCHITECTURE.md
55
+ # section 5a); exported_only gives the dependent-package view (section 4a).
56
+ # Resolved dependencies act as read-only layers below all own layers.
57
+ # extra_layers stack above everything (the reactor's writable overlay).
58
+ def merged_view: (?exported_only: bool exported_only, ?extra_layers: Array[MergedView::Layer] extra_layers) -> MergedView
59
+
38
60
  # The .cap file this package was loaded from, or nil when loaded
39
61
  # from a directory.
40
62
  def cap_file_path: () -> String?
41
63
 
42
64
  def content_files: () -> Array[String]
43
-
44
- def determine_load_type: (String | Pathname path) -> Symbol
45
-
46
- # Verifies the package against security.json (ARCHITECTURE.md
47
- # section 6). Returns a list of typed errors; empty when no
48
- # security.json is present or all checksums match.
49
- def verify_integrity: () -> Array[Security::ChecksumMismatch | Security::MissingFile | Security::UncheckedFile]
50
-
51
- def verify_integrity!: () -> void
52
65
  end
53
66
  end
@@ -18,6 +18,15 @@ module Capsium
18
18
 
19
19
  def compress_package: (Package package, String cap_file) -> void
20
20
 
21
+ # Unpacks a .cap into a temporary directory, yields it for read-only
22
+ # inspection and returns the block's value.
23
+ def with_unpacked_cap: (String cap_path) { (String dir) -> untyped } -> untyped
24
+
25
+ # Unpacks a .cap into a temporary directory, yields it for
26
+ # modification, then recompresses the result back over cap_path.
27
+ # Returns cap_path.
28
+ def transform_cap: (String cap_path) { (String dir) -> untyped } -> String
29
+
21
30
  def relative_path_current: (String absolute_path) -> String
22
31
  end
23
32
  end
@@ -0,0 +1,40 @@
1
+ module Capsium
2
+ class Reactor
3
+ # Request authentication and route authorization for the reactor
4
+ # (05x-authentication, ARCHITECTURE.md section 4b).
5
+ class Authenticator
6
+ LOGIN_PATH: String
7
+
8
+ attr_reader authentication: Package::Authentication
9
+ attr_reader deploy: Deploy
10
+
11
+ def initialize: (Package::Authentication authentication,
12
+ deploy: Deploy deploy, package_path: String package_path,
13
+ ?base_url: String? base_url,
14
+ ?state_file: String? state_file) -> void
15
+
16
+ def enabled?: () -> bool
17
+
18
+ # The paths this authenticator answers itself (OAuth2 login and
19
+ # callback), mounted by the reactor.
20
+ def endpoints: () -> Array[String]
21
+
22
+ def endpoint?: (String path) -> bool
23
+
24
+ def serve_endpoint: (untyped request, untyped response) -> untyped
25
+
26
+ # The identity for a request — from the session cookie or Basic
27
+ # credentials — or nil. An identity always carries "roles"
28
+ # (possibly empty).
29
+ def authenticate: (untyped request) -> Hash[String, untyped]?
30
+
31
+ # 401, with a Basic challenge when basicAuth is enabled.
32
+ def challenge: (untyped response) -> untyped
33
+
34
+ # :ok, :unauthenticated or :forbidden for a route's accessControl
35
+ # ({"roles": [...], "authenticationRequired": bool}).
36
+ def authorize: (Hash[String, untyped]? identity,
37
+ Hash[String, untyped]? access_control) -> (:ok | :unauthenticated | :forbidden)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,19 @@
1
+ module Capsium
2
+ class Reactor
3
+ # Content writes over the mount's overlay (ARCHITECTURE.md section
4
+ # 5a): PUT creates/overwrites a content file, DELETE records a
5
+ # tombstone. Text bodies only for v1.
6
+ class ContentApi
7
+ include Responses
8
+
9
+ WRITE_METHODS: Array[String]
10
+ CONTENT_PREFIX: String
11
+
12
+ def self.write_method?: (String method) -> bool
13
+
14
+ def initialize: (Mount mount) -> void
15
+
16
+ def handle: (String inner_path, untyped request, untyped response) -> untyped
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ module Capsium
2
+ class Reactor
3
+ # REST CRUD over a mount's datasets (ARCHITECTURE.md sections 5 and
4
+ # 5a): JSON item append/read/replace/delete against the
5
+ # overlay-merged collection.
6
+ class DataApi
7
+ include Responses
8
+
9
+ PREFIX: String
10
+ COLLECTION_PATTERN: Regexp
11
+ ITEM_PATTERN: Regexp
12
+
13
+ def self.path?: (String inner_path) -> bool
14
+
15
+ def initialize: (Mount mount) -> void
16
+
17
+ # The declared dataset route a data path addresses, or nil.
18
+ def route_for: (String inner_path) -> Package::Route?
19
+
20
+ def handle: (String inner_path, untyped request, untyped response) -> untyped
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,34 @@
1
+ module Capsium
2
+ class Reactor
3
+ # Reactor-side deployment configuration (deploy.json): everything
4
+ # that must NOT ship in the package — OAuth2 client secrets, the
5
+ # session signing secret, role assignments, an alternate htpasswd
6
+ # file, the public base URL.
7
+ class Deploy
8
+ FILE: String
9
+ ENV_VAR: String
10
+
11
+ attr_reader config: Hash[String, untyped]
12
+
13
+ # Loads from an explicit path, an already-parsed Hash, or the
14
+ # CAPSIUM_DEPLOY environment variable; empty when unconfigured.
15
+ def self.load: (Hash[String, untyped] | String? source) -> Deploy
16
+
17
+ def initialize: (Hash[String, untyped] config) -> void
18
+
19
+ def authentication: () -> Hash[String, untyped]
20
+
21
+ def base_url: () -> String?
22
+
23
+ def client_secret: () -> String?
24
+
25
+ def session_secret: () -> String?
26
+
27
+ def passwd_file: () -> String?
28
+
29
+ # Role assignments keyed by identity name (basic-auth username,
30
+ # OAuth2 email or subject): {"alice": ["admin", "user"]}.
31
+ def roles: () -> Hash[String, Array[String]]
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ module Capsium
2
+ class Reactor
3
+ # Serving of the reactor-level endpoints (introspection reports,
4
+ # per-package save), mixed into Reactor.
5
+ module Endpoints
6
+ include Responses
7
+
8
+ private
9
+
10
+ def serve_introspection: (untyped request, untyped response) -> untyped
11
+
12
+ # POST /package/<name>/save: folds base plus overlay into a new .cap.
13
+ def serve_package_save: (untyped request, untyped response) -> untyped
14
+ end
15
+ end
16
+ end