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,30 @@
1
+ module Capsium
2
+ # A small thread-safe ring buffer of timestamped log entries with a
3
+ # fixed capacity: when full, the oldest entry is dropped.
4
+ class LogBuffer
5
+ DEFAULT_CAPACITY: Integer
6
+
7
+ # One buffer entry: a UTC timestamp and a message.
8
+ class Entry
9
+ def self.new: (timestamp: Time, message: String) -> void
10
+
11
+ def timestamp: () -> Time
12
+ def message: () -> String
13
+
14
+ # "2026-07-19T15:00:00Z message"
15
+ def line: () -> String
16
+ end
17
+
18
+ attr_reader capacity: Integer
19
+
20
+ def initialize: (?capacity: Integer capacity) -> void
21
+
22
+ def add: (String message, ?timestamp: Time timestamp) -> LogBuffer
23
+
24
+ # The last n entries, oldest first.
25
+ def last: (Integer count) -> Array[Entry]
26
+
27
+ # The last n entries as formatted lines, oldest first.
28
+ def lines: (Integer count) -> Array[String]
29
+ end
30
+ end
@@ -0,0 +1,23 @@
1
+ module Capsium
2
+ class Package
3
+ # Loads a package's authentication.json (ARCHITECTURE.md section 4b).
4
+ class Authentication
5
+ attr_reader path: String
6
+ attr_reader config: AuthenticationConfig?
7
+
8
+ def initialize: (String path) -> void
9
+
10
+ def present?: () -> bool
11
+
12
+ def basic_auth: () -> BasicAuthConfig?
13
+
14
+ def oauth2: () -> OAuth2Config?
15
+
16
+ # Whether any authentication method is enabled.
17
+ def enabled?: () -> bool
18
+
19
+ def to_json: (*untyped args) -> String
20
+ def to_hash: () -> Hash[String, untyped]
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,71 @@
1
+ module Capsium
2
+ class Package
3
+ # The "basicAuth" object of authentication.json (ARCHITECTURE.md
4
+ # section 4b): Apache-style Basic authentication verified against an
5
+ # htpasswd file.
6
+ class BasicAuthConfig
7
+ attr_accessor enabled: bool
8
+ attr_accessor passwd_file: String?
9
+ attr_accessor realm: String
10
+
11
+ def initialize: (?enabled: bool enabled, ?passwd_file: String? passwd_file,
12
+ ?realm: String? realm) -> void
13
+
14
+ def enabled?: () -> bool
15
+
16
+ def to_json: (*untyped args) -> String
17
+ def to_hash: () -> Hash[String, untyped]
18
+ end
19
+
20
+ # The "oauth2" object of authentication.json: an OAuth2
21
+ # authorization-code flow. The client secret is NEVER read from the
22
+ # package — it comes from deploy.json or reactor configuration.
23
+ class OAuth2Config
24
+ attr_accessor enabled: bool
25
+ attr_accessor provider: String?
26
+ attr_accessor client_id: String?
27
+ attr_accessor authorization_url: String?
28
+ attr_accessor token_url: String?
29
+ attr_accessor userinfo_url: String?
30
+ attr_accessor redirect_path: String
31
+ attr_accessor scopes: Array[String]
32
+
33
+ def initialize: (?enabled: bool enabled, ?provider: String? provider,
34
+ ?client_id: String? client_id,
35
+ ?authorization_url: String? authorization_url,
36
+ ?token_url: String? token_url,
37
+ ?userinfo_url: String? userinfo_url,
38
+ ?redirect_path: String? redirect_path,
39
+ ?scopes: Array[String] scopes) -> void
40
+
41
+ def enabled?: () -> bool
42
+
43
+ def to_json: (*untyped args) -> String
44
+ def to_hash: () -> Hash[String, untyped]
45
+ end
46
+
47
+ # The "authentication" object of authentication.json.
48
+ class AuthenticationData
49
+ attr_accessor basic_auth: BasicAuthConfig?
50
+ attr_accessor oauth2: OAuth2Config?
51
+
52
+ def initialize: (?basic_auth: BasicAuthConfig? basic_auth,
53
+ ?oauth2: OAuth2Config? oauth2) -> void
54
+
55
+ def to_json: (*untyped args) -> String
56
+ def to_hash: () -> Hash[String, untyped]
57
+ end
58
+
59
+ # Canonical authentication.json model (ARCHITECTURE.md section 4b).
60
+ class AuthenticationConfig
61
+ attr_accessor authentication: AuthenticationData?
62
+
63
+ def initialize: (?authentication: AuthenticationData? authentication) -> void
64
+
65
+ def self.from_json: (String json) -> instance
66
+
67
+ def to_json: (*untyped args) -> String
68
+ def to_hash: () -> Hash[String, untyped]
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,43 @@
1
+ module Capsium
2
+ class Package
3
+ # Bundled dependencies of an encapsulated (self-contained) package:
4
+ # dependency .cap files embedded under packages/ with a
5
+ # packages/index.json manifest mapping each dependency GUID to its
6
+ # file, version and SHA-256. Bundled dependencies resolve first at
7
+ # load time, before the store -> registry chain.
8
+ class Bundle
9
+ DIRECTORY: String
10
+ INDEX_FILE: String
11
+
12
+ # One bundled dependency: the absolute .cap path, its version and
13
+ # the SHA-256 recorded in the manifest.
14
+ class Entry
15
+ def self.new: (String file, String version, String sha256) -> void
16
+
17
+ def file: () -> String
18
+ def version: () -> String
19
+ def sha256: () -> String
20
+ end
21
+
22
+ attr_reader entries: Hash[String, Entry]
23
+
24
+ def initialize: (String? package_path, ?Hash[String, Entry]? entries) -> void
25
+
26
+ # Embeds the resolved dependency .cap files (guid => .cap path
27
+ # pairs) into the package directory at package_path and writes the
28
+ # packages/index.json manifest. Returns the manifest hash.
29
+ def self.write: (String package_path, Array[[String, String]] resolutions) -> Hash[String, Hash[String, String]]
30
+
31
+ # Whether this bundle embeds any dependency.
32
+ def present?: () -> bool
33
+
34
+ # Merges an inherited (ancestor) bundle underneath this one; own
35
+ # entries win on GUID conflicts.
36
+ def merged_with: (Bundle? other) -> Bundle
37
+
38
+ # The absolute .cap path for a bundled dependency, or nil when the
39
+ # GUID is not bundled.
40
+ def resolve_path: (String guid, String range, chain: Array[String] chain) -> String?
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,62 @@
1
+ module Capsium
2
+ class Package
3
+ # Encrypts and decrypts whole Capsium packages (05x-packaging
4
+ # "Encryption", 05x-security "Encrypted information"). An encrypted
5
+ # .cap is a zip containing metadata.json (cleartext), signature.json
6
+ # (the encryption envelope) and package.enc (AES-256-GCM of the inner
7
+ # plaintext .cap zip). The random DEK is wrapped with the recipient's
8
+ # RSA public key using OAEP with SHA-256. OCB/OpenPGP are out of
9
+ # scope.
10
+ class Cipher
11
+ ALGORITHM: String
12
+ KEY_MANAGEMENT: String
13
+ CIPHER_NAME: String
14
+ ENCRYPTED_FILE: String
15
+ ENVELOPE_FILE: String
16
+ RSA_OPTIONS: Hash[String, String]
17
+
18
+ # Structural problems: unreadable input, missing zip entries,
19
+ # unsupported envelope algorithms, unloadable keys.
20
+ class CipherError < Capsium::Error
21
+ end
22
+
23
+ # An encrypted package was opened without a private key.
24
+ class KeyRequiredError < CipherError
25
+ end
26
+
27
+ # Decryption failed: wrong private key or tampered ciphertext.
28
+ class DecryptionError < CipherError
29
+ end
30
+
31
+ # Whether the path (.cap file or uncompressed directory) is an
32
+ # encrypted package, i.e. contains package.enc.
33
+ def self.encrypted?: (String | Pathname path) -> bool
34
+
35
+ # The keyManagement declared by the encryption envelope of the
36
+ # path (.cap file or uncompressed directory), or nil when the
37
+ # envelope is absent or unreadable.
38
+ def self.key_management: (String | Pathname path) -> String?
39
+
40
+ # The cipher instance matching the envelope's keyManagement:
41
+ # OpenPgpCipher for OpenPGP envelopes, the RSA cipher otherwise.
42
+ def self.for_encrypted: (String | Pathname path) -> Cipher
43
+
44
+ # Encrypts the package at source_path (a .cap file, or a package
45
+ # directory which is packed first) for the recipient's RSA public
46
+ # key (or X.509 certificate) and writes the encrypted .cap to
47
+ # output_path. Returns output_path.
48
+ def encrypt: (String source_path, String public_key_path, String output_path) -> String
49
+
50
+ # Decrypts the encrypted package at encrypted_path (.cap file or
51
+ # uncompressed directory) with the recipient's RSA private key and
52
+ # writes the plaintext .cap to output_path. Returns output_path.
53
+ def decrypt: (String | Pathname encrypted_path, String private_key_path, String output_path) -> String
54
+
55
+ # Decrypts an encrypted package into a fresh temporary directory
56
+ # and returns the directory path. The cipher is selected from the
57
+ # envelope's keyManagement: OpenPGP envelopes decrypt through
58
+ # OpenPgpCipher, everything else through the RSA cipher.
59
+ def self.decrypt_to_directory: (String | Pathname source_path, String private_key_path) -> String
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,21 @@
1
+ module Capsium
2
+ class Package
3
+ # Composite-package support (ARCHITECTURE.md section 4a), mixed into
4
+ # Package: metadata.dependencies resolution — bundled dependencies
5
+ # (Capsium::Package::Bundle) first, then the package store — and
6
+ # load-time validation of dependency resource references.
7
+ module Composition
8
+ private
9
+
10
+ def resolve_dependencies: ((Store | String)? store, (Capsium::Registry | String)? registry, Array[String] chain, ?Bundle? bundle) -> Array[ResolvedDependency]
11
+
12
+ def validate_dependency_references!: () -> void
13
+
14
+ def validate_dependency_reference: (Route route, ResolvedDependency dependency, String reference) -> void
15
+
16
+ def dependency_for: (String reference) -> ResolvedDependency?
17
+
18
+ def dependency_views: () -> Array[[String, MergedView]]
19
+ end
20
+ end
21
+ end
@@ -19,6 +19,14 @@ module Capsium
19
19
 
20
20
  def validate: () -> bool
21
21
 
22
+ # The parsed JSON schema for this dataset, or nil when none is
23
+ # declared or the file is unreadable.
24
+ def json_schema: () -> Hash[String, untyped]?
25
+
26
+ # Schema validation of an arbitrary candidate document (used by
27
+ # the reactor's writable overlay); empty when valid or schema-less.
28
+ def schema_errors_for: (untyped data) -> Array[String]
29
+
22
30
  # File-existence and schema validations; empty when valid.
23
31
  def validation_errors: () -> Array[String]
24
32
 
@@ -0,0 +1,53 @@
1
+ module Capsium
2
+ class Package
3
+ # Base error for composite-package dependency resolution
4
+ # (ARCHITECTURE.md section 4a).
5
+ class DependencyError < Capsium::Error
6
+ end
7
+
8
+ # No package in the store provides the dependency GUID.
9
+ class DependencyNotFoundError < DependencyError
10
+ end
11
+
12
+ # The store provides the GUID but no version satisfies the range.
13
+ class UnsatisfiableDependencyError < DependencyError
14
+ end
15
+
16
+ # A dependency cycle was detected while resolving.
17
+ class CircularDependencyError < DependencyError
18
+ end
19
+
20
+ # A dependent route references a dependency resource that is not
21
+ # exported (private) and therefore not visible to dependents.
22
+ class DependencyVisibilityError < DependencyError
23
+ end
24
+
25
+ # One resolved dependency: the declared GUID and range, the .cap it
26
+ # resolved to and the loaded package.
27
+ class ResolvedDependency
28
+ def self.new: (String guid, String range, String path, Package package) -> void
29
+
30
+ def guid: () -> String
31
+ def range: () -> String
32
+ def path: () -> String
33
+ def package: () -> Package
34
+ def version: () -> String?
35
+ end
36
+
37
+ # Orchestrates metadata.dependencies resolution against a package
38
+ # store: circularity checks plus store lookup, with a registry as
39
+ # install fallback for store misses.
40
+ class DependencyResolver
41
+ attr_reader store: Store
42
+ attr_reader registry: Capsium::Registry?
43
+
44
+ def initialize: (Store | String? store,
45
+ ?registry: (Capsium::Registry | String)? registry) -> void
46
+
47
+ # The newest store .cap satisfying the dependency, installing from
48
+ # the configured registry on a store miss and raising typed errors
49
+ # for circular, missing or unsatisfiable dependencies.
50
+ def resolve_path: (String guid, String range, chain: Array[String] chain) -> String
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,39 @@
1
+ module Capsium
2
+ class Package
3
+ # The "encryption" object of signature.json in an encrypted package:
4
+ # the AES-256-GCM envelope. keyManagement selects how the DEK is
5
+ # protected: "RSA-OAEP-SHA256" carries it Base64-wrapped in
6
+ # encryptedDek; "OpenPGP" carries an armored OpenPGP message
7
+ # containing the DEK in message. iv/authTag are Base64.
8
+ class EncryptionEnvelope
9
+ attr_accessor algorithm: String?
10
+ attr_accessor key_management: String?
11
+ attr_accessor encrypted_dek: String?
12
+ attr_accessor message: String?
13
+ attr_accessor iv: String?
14
+ attr_accessor auth_tag: String?
15
+
16
+ def initialize: (?algorithm: String? algorithm,
17
+ ?key_management: String? key_management,
18
+ ?encrypted_dek: String? encrypted_dek,
19
+ ?message: String? message,
20
+ ?iv: String? iv,
21
+ ?auth_tag: String? auth_tag) -> void
22
+
23
+ def to_json: (*untyped args) -> String
24
+ def to_hash: () -> Hash[String, untyped]
25
+ end
26
+
27
+ # The signature.json file of an encrypted package.
28
+ class EncryptionConfig
29
+ attr_accessor encryption: EncryptionEnvelope?
30
+
31
+ def initialize: (?encryption: EncryptionEnvelope? encryption) -> void
32
+
33
+ def self.from_json: (String json) -> instance
34
+
35
+ def to_json: (*untyped args) -> String
36
+ def to_hash: () -> Hash[String, untyped]
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,47 @@
1
+ module Capsium
2
+ class Package
3
+ # The merged (overlay) view of a package's content, per ARCHITECTURE.md
4
+ # section 5a. Shared by Package (validation) and Reactor (serving).
5
+ class MergedView
6
+ # A single read layer: an absolute root directory mirroring the
7
+ # content/ tree, plus its parsed tombstone set.
8
+ class Layer
9
+ def self.new: (String root, String visibility, Set[String] tombstones) -> void
10
+
11
+ def file?: (String content_relative_path) -> bool
12
+
13
+ def absolute: (String content_relative_path) -> String
14
+
15
+ def root: () -> String
16
+
17
+ def visibility: () -> String
18
+
19
+ def tombstones: () -> Set[String]
20
+ end
21
+
22
+ TOMBSTONE_FILE: String
23
+ CONTENT_PREFIX: String
24
+
25
+ attr_reader package_path: String
26
+ attr_reader layers: Array[Layer]
27
+ attr_reader dependency_views: Array[[String, MergedView]]
28
+
29
+ def initialize: (String package_path, storage: Storage storage,
30
+ manifest: Manifest manifest,
31
+ ?dependency_views: Array[[String, MergedView]] dependency_views,
32
+ ?exported_only: bool exported_only,
33
+ ?extra_layers: Array[Layer] extra_layers) -> void
34
+
35
+ # The absolute filesystem path serving a resource reference: a
36
+ # package-relative content path ("content/app.js"), or a dependency
37
+ # reference ("<dependency-guid>/content/app.js"). nil when no layer
38
+ # provides the path or it is tombstoned. Non-content paths never
39
+ # resolve through the view.
40
+ def resolve: (String? reference) -> String?
41
+
42
+ # The [GUID, view] pair whose GUID prefixes the reference (longest
43
+ # GUID first, so nested GUIDs address the innermost dependency).
44
+ def dependency_pair_for: (String reference) -> [String, MergedView]?
45
+ end
46
+ end
47
+ end
@@ -21,6 +21,7 @@ module Capsium
21
21
  def license: () -> String?
22
22
  def repository: () -> Repository?
23
23
  def dependencies: () -> Hash[String, String]
24
+ def read_only: () -> bool?
24
25
  end
25
26
  end
26
27
  end
@@ -0,0 +1,38 @@
1
+ module Capsium
2
+ class Package
3
+ # OpenPGP support (signatures and encryption) through the rnp gem's
4
+ # binding to librnp. rnp is a soft dependency: it is only required
5
+ # when an OpenPGP feature is used, and this module is the single
6
+ # place that loads it.
7
+ module OpenPgp
8
+ # The rnp gem or the librnp shared library is not available.
9
+ class OpenPgpUnavailableError < Capsium::Error
10
+ end
11
+
12
+ # A key file is unreadable or holds no suitable OpenPGP key.
13
+ class KeyError < Capsium::Error
14
+ end
15
+
16
+ UNAVAILABLE_MESSAGE: String
17
+
18
+ # One loaded key file: the Rnp context holding the key material
19
+ # and the selected key within it.
20
+ class LoadedKey
21
+ def self.new: (untyped rnp, untyped key) -> void
22
+
23
+ def rnp: () -> untyped
24
+ def key: () -> untyped
25
+ end
26
+
27
+ # A fresh Rnp context. Raises OpenPgpUnavailableError when the rnp
28
+ # gem or librnp cannot be loaded.
29
+ def self.rnp: () -> untyped
30
+
31
+ # Loads the OpenPGP key at key_path (armored or binary, public or
32
+ # secret — the format is auto-detected). With secret: true the
33
+ # selected key must hold secret key material. Raises KeyError when
34
+ # the file is unreadable or holds no suitable key.
35
+ def self.load_key: (String key_path, ?secret: bool secret) -> LoadedKey
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,15 @@
1
+ module Capsium
2
+ class Package
3
+ # Encrypts and decrypts whole Capsium packages with OpenPGP key
4
+ # management (ARCHITECTURE.md section 6b) through librnp: the DEK
5
+ # travels as an armored OpenPGP message in the signature.json
6
+ # envelope (keyManagement "OpenPGP"); the AES-256-GCM content
7
+ # encryption and the encrypted .cap layout are the Cipher's.
8
+ class OpenPgpCipher < Cipher
9
+ KEY_MANAGEMENT: String
10
+
11
+ # The OpenPGP symmetric cipher protecting the DEK message.
12
+ CIPHER_NAME: String
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,46 @@
1
+ module Capsium
2
+ class Package
3
+ # Signs and verifies Capsium packages with OpenPGP detached
4
+ # signatures (ARCHITECTURE.md section 6a) through librnp. Parallel
5
+ # to the RSA-SHA256/X.509 Signer with the same construction
6
+ # semantics and the same error taxonomy.
7
+ class OpenPgpSigner
8
+ ALGORITHM: String
9
+ CERTIFICATE_TYPE: String
10
+ HASH: String
11
+
12
+ # Package-relative name of the embedded armored OpenPGP public key
13
+ # recorded in security.json digitalSignatures.publicKey.
14
+ PUBLIC_KEY_FILE: String
15
+
16
+ attr_reader package_path: String
17
+
18
+ def initialize: (String package_path) -> void
19
+
20
+ # Signs a package directory in place, or a .cap file (unpacked,
21
+ # signed, recompressed), with the OpenPGP secret key at
22
+ # secret_key_path. Returns the signed path.
23
+ def self.sign_package: (String path, String secret_key_path) -> String
24
+
25
+ # Verifies the declared OpenPGP signature of a package directory
26
+ # or .cap file (false on mismatch; raises typed
27
+ # Signer::SignatureError subclasses on structural problems).
28
+ def self.verify_package: (String path, ?String? public_key_path) -> bool
29
+
30
+ # Signs the package directory in place: embeds the armored public
31
+ # key, regenerates security.json (checksums plus digitalSignatures
32
+ # with certificateType "OpenPGP") and writes the armored detached
33
+ # OpenPGP signature to signature.sig. Returns the path of the
34
+ # written signature file.
35
+ def sign: (String secret_key_path) -> String
36
+
37
+ # True when the declared signature verifies against the payload;
38
+ # false on mismatch.
39
+ def verify: (?String? public_key_path) -> bool
40
+
41
+ def verify!: (?String? public_key_path) -> bool
42
+
43
+ def signed?: () -> bool
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,23 @@
1
+ module Capsium
2
+ class Package
3
+ # Package-source preparation (ARCHITECTURE.md section 1), mixed into
4
+ # Package: directory/.cap/encrypted-cap detection, extraction and
5
+ # decryption into a readable package directory.
6
+ module Preparation
7
+ def prepare_package: (Pathname path) -> (String | Pathname)
8
+
9
+ def decompress_cap_file: (String | Pathname file_path) -> String
10
+
11
+ # Decrypts an encrypted package (.cap file or uncompressed directory)
12
+ # into a temporary directory and returns its path. Raises
13
+ # Cipher::KeyRequiredError when no decryption_key was given.
14
+ def decrypt_cap_file: (String | Pathname source_path) -> String
15
+
16
+ def determine_load_type: (String | Pathname path) -> Symbol
17
+
18
+ # Whether the package was loaded from an encrypted source
19
+ # (ARCHITECTURE.md section 6b layout).
20
+ def encrypted?: () -> bool
21
+ end
22
+ end
23
+ end
@@ -1,8 +1,22 @@
1
1
  module Capsium
2
2
  class Package
3
+ # The "responseRewrite" object of an inherited route (05x-routing
4
+ # section "Route Inheritance"): replaces the served body and/or
5
+ # overrides response headers.
6
+ class ResponseRewrite
7
+ attr_accessor body: String?
8
+ attr_accessor headers: Hash[String, String]?
9
+
10
+ def initialize: (?body: String? body, ?headers: Hash[String, String]? headers) -> void
11
+
12
+ def to_json: (*untyped args) -> String
13
+ def to_hash: () -> Hash[String, untyped]
14
+ end
15
+
3
16
  # A single route entry (ARCHITECTURE.md section 4). Kinds are
4
17
  # discriminated by key, MECE: static file, dataset route, or dynamic
5
- # handler (accepted-and-ignored; reactors respond 501).
18
+ # handler (accepted-and-ignored; reactors respond 501). Route
19
+ # inheritance attributes per 05x-routing.
6
20
  class Route
7
21
  DATASET_PATH_PREFIX: String
8
22
 
@@ -15,6 +29,10 @@ module Capsium
15
29
  attr_accessor access_control: Hash[String, untyped]?
16
30
  attr_accessor http_method: String?
17
31
  attr_accessor handler: String?
32
+ attr_accessor remap: String?
33
+ attr_accessor response_rewrite: ResponseRewrite?
34
+ attr_accessor response_headers: Hash[String, String]?
35
+ attr_accessor request_headers: Hash[String, String]?
18
36
 
19
37
  def initialize: (?path: String? path, ?resource: String? resource,
20
38
  ?headers: Hash[String, String]? headers,
@@ -22,7 +40,10 @@ module Capsium
22
40
  ?visibility: String? visibility, ?dataset: String? dataset,
23
41
  ?access_control: Hash[String, untyped]? access_control,
24
42
  ?http_method: String? http_method,
25
- ?handler: String? handler) -> void
43
+ ?handler: String? handler, ?remap: String? remap,
44
+ ?response_rewrite: ResponseRewrite? response_rewrite,
45
+ ?response_headers: Hash[String, String]? response_headers,
46
+ ?request_headers: Hash[String, String]? request_headers) -> void
26
47
 
27
48
  def self.from_json: (String json) -> instance
28
49
 
@@ -32,11 +53,23 @@ module Capsium
32
53
 
33
54
  def handler_route?: () -> bool
34
55
 
56
+ # The URL path this route answers at: the remapped path when the
57
+ # route remaps an inherited route, its own path otherwise.
58
+ def serving_path: () -> String?
59
+
60
+ # Whether the resource addresses content of a dependency package
61
+ # ("<dependency-guid>/<path>" — a URI rather than a
62
+ # package-relative path).
63
+ def dependency_reference?: () -> bool
64
+
65
+ # Whether the route carries route-inheritance attributes.
66
+ def inherited?: () -> bool
67
+
35
68
  def fs_path: (String package_path) -> String?
36
69
 
37
70
  def mime: (Manifest manifest) -> String?
38
71
 
39
- def validate_target: (String package_path, Storage storage) -> void
72
+ def validate_target: (String package_path, Storage storage, ?merged_view: MergedView? merged_view) -> void
40
73
 
41
74
  def to_json: (*untyped args) -> String
42
75
  def to_hash: () -> Hash[String, untyped]
@@ -2,7 +2,7 @@ module Capsium
2
2
  class Package
3
3
  # Loads, generates and verifies security.json (ARCHITECTURE.md
4
4
  # section 6). Checksums cover every file in the package except
5
- # security.json itself.
5
+ # security.json itself and signature.sig.
6
6
  class Security
7
7
  class IntegrityError < Capsium::Error
8
8
  end
@@ -42,14 +42,21 @@ module Capsium
42
42
 
43
43
  def initialize: (String path, ?SecurityConfig? config) -> void
44
44
 
45
- def self.generate: (String package_path) -> Security
45
+ def self.generate: (String package_path, ?digital_signatures: DigitalSignatures? digital_signatures) -> Security
46
46
 
47
47
  def self.checksums_for: (String package_path) -> Hash[String, String]
48
48
 
49
49
  def self.package_files: (String package_path) -> Array[String]
50
50
 
51
+ def self.excluded?: (String relative_path) -> bool
52
+
51
53
  def present?: () -> bool
52
54
 
55
+ # The declared digitalSignatures block, or nil when absent.
56
+ def digital_signatures: () -> DigitalSignatures?
57
+
58
+ def signed?: () -> bool
59
+
53
60
  def checksums: () -> Hash[String, String]
54
61
 
55
62
  def verify: (String package_path) -> Array[ChecksumMismatch | MissingFile | UncheckedFile]
@@ -15,13 +15,16 @@ module Capsium
15
15
  def to_hash: () -> Hash[String, untyped]
16
16
  end
17
17
 
18
- # The "digitalSignatures" object of security.json (parsed only;
19
- # signing is a later phase).
18
+ # The "digitalSignatures" object of security.json: certificateType
19
+ # selects the signature scheme (absent = RSA-SHA256/X.509,
20
+ # "OpenPGP" = OpenPGP).
20
21
  class DigitalSignatures
22
+ attr_accessor certificate_type: String?
21
23
  attr_accessor public_key: String?
22
24
  attr_accessor signature_file: String?
23
25
 
24
- def initialize: (?public_key: String? public_key,
26
+ def initialize: (?certificate_type: String? certificate_type,
27
+ ?public_key: String? public_key,
25
28
  ?signature_file: String? signature_file) -> void
26
29
 
27
30
  def to_json: (*untyped args) -> String