capsium 0.2.0 → 0.3.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -0
  3. data/CHANGELOG.md +125 -0
  4. data/README.adoc +332 -5
  5. data/capsium.gemspec +1 -0
  6. data/lib/capsium/cli/formatting.rb +27 -0
  7. data/lib/capsium/cli/package.rb +68 -6
  8. data/lib/capsium/cli/reactor.rb +9 -1
  9. data/lib/capsium/cli.rb +1 -0
  10. data/lib/capsium/package/authentication.rb +38 -0
  11. data/lib/capsium/package/authentication_config.rb +78 -0
  12. data/lib/capsium/package/cipher.rb +197 -0
  13. data/lib/capsium/package/composition.rb +77 -0
  14. data/lib/capsium/package/dependency_resolver.rb +61 -0
  15. data/lib/capsium/package/encryption_config.rb +38 -0
  16. data/lib/capsium/package/merged_view.rb +168 -0
  17. data/lib/capsium/package/preparation.rb +81 -0
  18. data/lib/capsium/package/routes_config.rb +60 -6
  19. data/lib/capsium/package/security.rb +21 -4
  20. data/lib/capsium/package/signer.rb +201 -0
  21. data/lib/capsium/package/storage_config.rb +29 -1
  22. data/lib/capsium/package/store.rb +109 -0
  23. data/lib/capsium/package/testing/config_test.rb +70 -0
  24. data/lib/capsium/package/testing/context.rb +11 -0
  25. data/lib/capsium/package/testing/data_validation_test.rb +63 -0
  26. data/lib/capsium/package/testing/file_test.rb +45 -0
  27. data/lib/capsium/package/testing/report.rb +33 -0
  28. data/lib/capsium/package/testing/route_test.rb +64 -0
  29. data/lib/capsium/package/testing/test_case.rb +60 -0
  30. data/lib/capsium/package/testing/test_suite.rb +86 -0
  31. data/lib/capsium/package/testing.rb +20 -0
  32. data/lib/capsium/package/validator.rb +14 -3
  33. data/lib/capsium/package/verification.rb +32 -0
  34. data/lib/capsium/package/version.rb +204 -0
  35. data/lib/capsium/package.rb +59 -75
  36. data/lib/capsium/packager.rb +34 -1
  37. data/lib/capsium/reactor/authenticator.rb +180 -0
  38. data/lib/capsium/reactor/deploy.rb +71 -0
  39. data/lib/capsium/reactor/htpasswd.rb +154 -0
  40. data/lib/capsium/reactor/introspection.rb +4 -1
  41. data/lib/capsium/reactor/oauth2.rb +106 -0
  42. data/lib/capsium/reactor/serving.rb +75 -0
  43. data/lib/capsium/reactor/session.rb +82 -0
  44. data/lib/capsium/reactor.rb +43 -41
  45. data/lib/capsium/thor_ext.rb +1 -1
  46. data/lib/capsium/version.rb +1 -1
  47. data/sig/capsium/package/authentication.rbs +23 -0
  48. data/sig/capsium/package/authentication_config.rbs +71 -0
  49. data/sig/capsium/package/cipher.rbs +51 -0
  50. data/sig/capsium/package/composition.rbs +20 -0
  51. data/sig/capsium/package/dependency_resolver.rbs +49 -0
  52. data/sig/capsium/package/encryption_config.rbs +35 -0
  53. data/sig/capsium/package/merged_view.rbs +46 -0
  54. data/sig/capsium/package/preparation.rbs +23 -0
  55. data/sig/capsium/package/routes_config.rbs +36 -3
  56. data/sig/capsium/package/security.rbs +9 -2
  57. data/sig/capsium/package/signer.rbs +59 -0
  58. data/sig/capsium/package/storage_config.rbs +26 -2
  59. data/sig/capsium/package/store.rbs +37 -0
  60. data/sig/capsium/package/testing/config_test.rbs +21 -0
  61. data/sig/capsium/package/testing/context.rbs +14 -0
  62. data/sig/capsium/package/testing/data_validation_test.rbs +22 -0
  63. data/sig/capsium/package/testing/file_test.rbs +17 -0
  64. data/sig/capsium/package/testing/report.rbs +20 -0
  65. data/sig/capsium/package/testing/route_test.rbs +22 -0
  66. data/sig/capsium/package/testing/test_case.rbs +41 -0
  67. data/sig/capsium/package/testing/test_suite.rbs +20 -0
  68. data/sig/capsium/package/testing.rbs +7 -0
  69. data/sig/capsium/package/verification.rbs +24 -0
  70. data/sig/capsium/package/version.rbs +39 -0
  71. data/sig/capsium/package.rbs +25 -17
  72. data/sig/capsium/packager.rbs +9 -0
  73. data/sig/capsium/reactor/authenticator.rbs +40 -0
  74. data/sig/capsium/reactor/deploy.rbs +34 -0
  75. data/sig/capsium/reactor/htpasswd.rbs +36 -0
  76. data/sig/capsium/reactor/oauth2.rbs +31 -0
  77. data/sig/capsium/reactor/serving.rbs +22 -0
  78. data/sig/capsium/reactor/session.rbs +36 -0
  79. data/sig/capsium/reactor.rbs +6 -1
  80. metadata +70 -1
@@ -0,0 +1,46 @@
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) -> void
33
+
34
+ # The absolute filesystem path serving a resource reference: a
35
+ # package-relative content path ("content/app.js"), or a dependency
36
+ # reference ("<dependency-guid>/content/app.js"). nil when no layer
37
+ # provides the path or it is tombstoned. Non-content paths never
38
+ # resolve through the view.
39
+ def resolve: (String? reference) -> String?
40
+
41
+ # The [GUID, view] pair whose GUID prefixes the reference (longest
42
+ # GUID first, so nested GUIDs address the innermost dependency).
43
+ def dependency_pair_for: (String reference) -> [String, MergedView]?
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]
@@ -0,0 +1,59 @@
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).
41
+ def self.verify_package: (String path, ?String? public_key_path) -> bool
42
+
43
+ # Signs the package directory in place: embeds the public key PEM,
44
+ # regenerates security.json (checksums plus digitalSignatures) and
45
+ # writes the raw RSA-SHA256 signature to signature.sig. Returns the
46
+ # path of the written signature file.
47
+ def sign: (String private_key_path, ?String? certificate_path) -> String
48
+
49
+ # True when the declared signature verifies against the payload;
50
+ # false on mismatch. Raises typed SignatureError subclasses on
51
+ # structural problems.
52
+ def verify: (?String? public_key_path) -> bool
53
+
54
+ def verify!: (?String? public_key_path) -> bool
55
+
56
+ def signed?: () -> bool
57
+ end
58
+ end
59
+ 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,37 @@
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
+ end
36
+ end
37
+ 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,24 @@
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 (RSA-SHA256) against the
17
+ # checksum-covered payload. True when the package is unsigned or the
18
+ # signature verifies; false on mismatch.
19
+ def verify_signature: () -> bool
20
+
21
+ def verify_signature!: () -> void
22
+ end
23
+ end
24
+ 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,38 @@ module Capsium
18
24
  attr_reader storage: Storage
19
25
  attr_reader security: Security
20
26
  attr_reader load_type: Symbol
21
-
22
- def initialize: (String | Pathname path, ?load_type: Symbol? load_type) -> void
23
-
24
- def prepare_package: (Pathname path) -> (String | Pathname)
27
+ attr_reader resolved_dependencies: Array[ResolvedDependency]
28
+ attr_reader authentication: Authentication
29
+
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. `dependency_chain` is internal: the ancestor GUIDs
34
+ # used for circular-dependency detection during recursive resolution.
35
+ def initialize: (String | Pathname path, ?load_type: Symbol? load_type,
36
+ ?decryption_key: String? decryption_key,
37
+ ?store: (Store | String)? store,
38
+ ?dependency_chain: Array[String] dependency_chain) -> void
25
39
 
26
40
  def solidify: () -> void
27
41
 
28
- def decompress_cap_file: (String | Pathname file_path) -> String
29
-
30
42
  def load_package: () -> void
31
43
 
32
- # Removes the temporary extraction directory of a .cap-loaded
33
- # package; a no-op for directory loads.
44
+ # Removes the temporary extraction directories of the package and its
45
+ # resolved dependencies; a no-op for directory loads.
34
46
  def cleanup: () -> void
35
47
 
36
48
  def datasets: () -> Array[Dataset]
37
49
 
50
+ # The merged content view across the storage layers (ARCHITECTURE.md
51
+ # section 5a); exported_only gives the dependent-package view (section 4a).
52
+ # Resolved dependencies act as read-only layers below all own layers.
53
+ def merged_view: (?exported_only: bool exported_only) -> MergedView
54
+
38
55
  # The .cap file this package was loaded from, or nil when loaded
39
56
  # from a directory.
40
57
  def cap_file_path: () -> String?
41
58
 
42
59
  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
60
  end
53
61
  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