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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9746224c88e5a4a3b2aabe9609af2348235d6efe09dcd37fa71c710f08594829
4
- data.tar.gz: fcbf1c6857f12bede912bb25d32dc83ddb84cd97757425dcf642af9e785e9478
3
+ metadata.gz: 4d5f0f655cb66b0e6b49edcb70ad68b8e6670e0ca2cb0d5a77aef84c609a3c6b
4
+ data.tar.gz: 3f4f17221db2c72db65c14faa2b5e3a31cf026e5c5b1e0ffc331af872b8b0111
5
5
  SHA512:
6
- metadata.gz: fce42689914eda35768a2dfa4dfa74b5c00e8b760ddb50cb6b44b810a20b1423e0a31392eaefff1270408c6ed83d4b56c10a895a54f609ff71afd117b6950194
7
- data.tar.gz: a45f5ea52966810c8ab183f0ec3407287eb7c87b34f82555242372d08b944edea2c372fdabe836f5e8ef0ff7ee793bed10fcc0a6ca21e486acd2466f7fbb1b93
6
+ metadata.gz: 4e3b9777dc6f0c498784959ab66cb89228657b6f8e85a43818c804c311371d86f78d9ffd3cf7f61e1ad743db08608fafdbe35d49d3936073f8d8281d66cd9816
7
+ data.tar.gz: 7cfb11cce09e70b03f94057c68adea6389022f50aa5b9573311f0cf0a8d606013bf60115e4f11bfb45a07460fd3dd5f22e93b34ef5dcb84cb052bc25c93cc6bb
data/.rubocop.yml CHANGED
@@ -26,8 +26,24 @@ Metrics/BlockLength:
26
26
  - 'spec/**/*'
27
27
  - 'capsium.gemspec'
28
28
 
29
+ # Spec helpers take the parameters the fixtures need.
30
+ Metrics/ModuleLength:
31
+ Exclude:
32
+ - 'spec/**/*'
33
+
34
+ # Spec helpers take the parameters the fixtures need; the reactor
35
+ # constructor takes one keyword per deploy-time concern (keyword args
36
+ # are self-documenting and not counted).
37
+ Metrics/ParameterLists:
38
+ Max: 7
39
+ CountKeywordArgs: false
40
+ Exclude:
41
+ - 'spec/**/*'
42
+
29
43
  Metrics/ClassLength:
30
- Max: 120
44
+ # The reactor (request dispatch + lifecycle) and the introspection
45
+ # endpoint set grew with the phase-3 follow-ons.
46
+ Max: 150
31
47
 
32
48
  Metrics/MethodLength:
33
49
  Max: 25
data/CHANGELOG.md CHANGED
@@ -1,5 +1,327 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Added
6
+
7
+ - OpenPGP signatures and encryption through librnp (new `rnp` gem
8
+ dependency, loaded lazily — a typed
9
+ `Capsium::Package::OpenPgp::OpenPgpUnavailableError` with
10
+ installation guidance is raised when the gem or librnp is missing):
11
+ - `Capsium::Package::OpenPgpSigner` (parallel to the RSA-SHA256/X.509
12
+ `Signer`, same construction semantics and `Signer::SignatureError`
13
+ taxonomy): `capsium package sign PATH --openpgp --key SEC.asc`
14
+ writes an armored detached OpenPGP signature (SHA-256) over the
15
+ canonical section-6a payload to `signature.sig`, embeds the armored
16
+ public key as `signature.pub.asc` and records
17
+ `"digitalSignatures": {"certificateType": "OpenPGP", ...}` in
18
+ security.json. Verification auto-detects the scheme from
19
+ `certificateType` (`Signer.verify_package`,
20
+ `Package#verify_signature`, the load-time signature gate and
21
+ `capsium package verify-signature` all dispatch); `--openpgp
22
+ --cert PUB.asc` verifies with an explicit OpenPGP public key.
23
+ - `Capsium::Package::OpenPgpCipher < Cipher` (same encrypted .cap
24
+ layout and AES-256-GCM content encryption): `capsium package
25
+ encrypt PATH --openpgp --recipient PUB.asc -o OUT.cap` protects the
26
+ DEK as an armored OpenPGP message
27
+ (`signature.json`: `{"encryption": {"algorithm": "AES-256-GCM",
28
+ "keyManagement": "OpenPGP", "message", "iv", "authTag"}}`).
29
+ Decryption auto-detects the key management from the envelope
30
+ (`Cipher.for_encrypted`, `Cipher.key_management`), and
31
+ `Package.new(decryption_key:)` accepts OpenPGP secret keys
32
+ (armored or binary) transparently. `Cipher`'s envelope/DEK handling
33
+ was refactored into subclass seams; the RSA-OAEP-SHA256 path is
34
+ unchanged.
35
+ - `Capsium::Package::OpenPgp` is the single lazy loader of the rnp
36
+ binding and the key-file loader (armor/format auto-detection,
37
+ `OpenPgp::KeyError` for unreadable or unsuitable keys).
38
+ - The OpenPGP specs skip cleanly when librnp cannot load, so CI
39
+ without librnp stays green; see README "OpenPGP support (librnp)".
40
+ - Encapsulated packages (bundled dependencies): `capsium package pack
41
+ --bundle-deps` (alias `--bundle`; `Packager#pack` option
42
+ `bundle_deps: true`) resolves every declared `metadata.dependencies`
43
+ entry through the store -> registry chain and embeds the resolved
44
+ `.cap` files inside the parent under `packages/`, producing a fully
45
+ self-contained package that activates with no store or registry. The
46
+ `packages/index.json` manifest maps each dependency GUID to its
47
+ embedded file, resolved version and SHA-256
48
+ (`Capsium::Package::Bundle`). Loading resolves bundled dependencies
49
+ FIRST — including from inside a parent `.cap` — before the store ->
50
+ registry chain, and passes the bundle down so re-declared transitive
51
+ dependencies resolve from it (one-level bundling policy: the parent's
52
+ `metadata.dependencies` must list the transitive closure). The
53
+ manifest SHA-256 is re-verified at resolution
54
+ (`Security::IntegrityError` on mismatch) and each bundled package's
55
+ own `security.json`/signature is verified at activation. `pack`
56
+ gained `--store`/`--registry` options; `capsium package validate`
57
+ resolves bundled dependency layers, so fallthrough routes of
58
+ encapsulated packages validate.
59
+
60
+ ## 0.5.0
61
+
62
+ ### Added
63
+
64
+ - Multiple mounted packages per reactor: `capsium reactor serve`
65
+ accepts multiple sources — positional arguments, repeatable
66
+ `--mount PATH=SOURCE` options and/or a JSON mount config
67
+ (`--config FILE`: `{"mounts": [{"path", "source", "store"?}]}`).
68
+ Default mount points: the first source at `/`, each additional one at
69
+ `/<metadata.name>/`; duplicate prefixes raise
70
+ `Capsium::Reactor::MountConflictError`. Requests dispatch by
71
+ longest-prefix mount matching (`Capsium::Reactor::Mount`); the
72
+ `/api/v1/introspect/*` endpoints aggregate ALL mounted packages,
73
+ `/package/<name>/{status,metadata,logs}` resolve by package name
74
+ (404 for unknown names), metrics/logs stay reactor-global, and
75
+ `Capsium::Reactor#cleanup` cleans up every mounted package.
76
+ Single-source invocation is unchanged.
77
+ - Writable packages (ARCHITECTURE.md section 5a): a mounted package
78
+ whose metadata does not declare `"readOnly": true` gets an
79
+ append-only overlay layer in the reactor workdir (`--workdir DIR`,
80
+ default a temporary directory removed on cleanup), always the
81
+ topmost layer of the merged view (`MergedView` gained
82
+ `extra_layers`). The immutable base never changes on disk and every
83
+ write is visible on the next request (hot-swap); overlay state
84
+ (content files, `.capsium-tombstones`, per-dataset JSON operation
85
+ logs) persists in the workdir.
86
+ - REST CRUD over datasets (`Capsium::Reactor::DataApi`):
87
+ `POST /api/v1/data/<dataset>` appends an item (201 + Location +
88
+ stored item; id convention is the `id` field else the 1-based index
89
+ as a string), `GET /<id>` reads one item, `PUT /<id>` replaces and
90
+ `DELETE /<id>` deletes. Statuses: 400 for malformed JSON, 403 with a
91
+ clear body on read-only packages, 404 for unknown datasets/items,
92
+ 405 for wrong verbs, 409 for duplicate/mismatched ids, 422 with the
93
+ schema errors when the candidate document violates the dataset's
94
+ JSON schema, 501 for SQLite datasets. Undeclared datasets are not
95
+ served and route-level `accessControl` still applies.
96
+ - Content writes (`Capsium::Reactor::ContentApi`): `PUT <route>`
97
+ creates/overwrites a content file (creating its route on demand;
98
+ text bodies only for v1) and `DELETE <route>` records a tombstone so
99
+ the path 404s even when a lower layer holds the file (404 when
100
+ nothing to delete).
101
+ - GraphQL (new `graphql` dependency): every mounted package with
102
+ datasets answers `<mount>/graphql` (POST, or GET with `?query=`)
103
+ with a schema auto-derived from the package's storage
104
+ (`Capsium::Reactor::GraphqlSchema`): a query field `<dataset>`
105
+ (list) with an optional `id:` argument (single item) plus
106
+ `create<Dataset>`/`update<Dataset>`/`delete<Dataset>` mutations
107
+ matching the REST semantics including schema validation. Item types
108
+ are inferred from the dataset's JSON schema when present, else map
109
+ to a permissive JSON scalar; SQLite datasets are skipped. Not-found
110
+ items, schema violations, duplicates and read-only mutations land in
111
+ the `errors` array (no 500s on user error).
112
+ - Save composite: `POST /package/<name>/save`
113
+ (`Capsium::Reactor::PackageSaver`) folds the base package plus its
114
+ overlay into a NEW versioned `.cap` (`<name>-<version+patch>.cap`)
115
+ in the workdir — overlay content files replace, tombstones delete,
116
+ dataset mutation logs replay into the dataset files, and manifest,
117
+ routes and security.json are regenerated — returning the `.cap`
118
+ path and its SHA-256. The saved package passes
119
+ `capsium package validate`. Read-only packages get 403, unknown
120
+ names 404, non-POST 405.
121
+
122
+ ### Changed
123
+
124
+ - `Capsium::Reactor::Introspection.new` now takes the list of served
125
+ packages (aggregation); `/introspect/status` `packagesLoaded`
126
+ reflects the mount count.
127
+ - The reactor dispatch, endpoint and response handling are split into
128
+ `Capsium::Reactor::Serving` (mount request serving),
129
+ `Capsium::Reactor::Endpoints` (reactor-level endpoints) and
130
+ `Capsium::Reactor::Responses` (HTTP response writers).
131
+ - WEBrick's `ProcHandler` (GET/POST/PUT only) is extended with
132
+ `do_DELETE`/`do_PATCH` aliases so the writable-package REST verbs
133
+ reach the mounted procs.
134
+ - `Metrics/ParameterLists` rubocop no longer counts keyword args (the
135
+ reactor constructor takes one keyword per deploy-time concern).
136
+
137
+ ## 0.4.0
138
+
139
+ ### Added
140
+
141
+ - Static registries (ARCHITECTURE.md section 4a follow-on): a registry
142
+ is a directory or a static https base URL holding an `index.json`
143
+ (`packages` -> GUID -> `name` + `versions` -> version -> `file`,
144
+ `sha256`, `size`) with `.cap` files stored relative to the registry
145
+ root, so any static host (GitHub Pages, S3, nginx) can serve one.
146
+ `Capsium::Registry.fetch(ref)` returns `Registry::Local` (read-write
147
+ directory) or `Registry::Remote` (read-only https base URL over
148
+ net/http with redirect following and timeouts; plain http for
149
+ loopback hosts only). `Registry#resolve` picks the newest version
150
+ satisfying a semver constraint via the built-in
151
+ `Version`/`VersionRange` matcher.
152
+ - `Registry::Local#push`: validates the `.cap` with
153
+ `Capsium::Package::Validator` (`Registry::InvalidPackageError` on
154
+ failure), copies it into the registry directory and atomically
155
+ rewrites `index.json` (tmp + rename) with recomputed sha256 and size.
156
+ - `Registry#install`: fetches the resolved `.cap`, verifies it against
157
+ the sha256 declared in the index (`Registry::ChecksumMismatchError`
158
+ on mismatch) and installs it into the package store as
159
+ `<name>-<version>.cap`, atomically updating the store's own
160
+ `index.json` (`Package::Store#install`). All registry failures are
161
+ typed `Registry::RegistryError` subclasses
162
+ (`RegistryNotConfiguredError`, `InvalidRegistryError`,
163
+ `InvalidPackageError`, `PackageNotFoundError`,
164
+ `UnsatisfiableConstraintError`, `ChecksumMismatchError`,
165
+ `FetchError`).
166
+ - `capsium package push PACKAGE --registry DIR` and
167
+ `capsium install GUID [--constraint RANGE] [--registry DIR_OR_URL]
168
+ [--store DIR]`; the registry defaults to `CAPSIUM_REGISTRY`, the
169
+ store to `CAPSIUM_STORE` (typed errors when unconfigured).
170
+ - `capsium reactor serve capsium://GUID` (install-then-serve with
171
+ `--registry`/`--store`/`--constraint`), and dependency-resolution
172
+ fallback for composite packages: `Package::DependencyResolver` (and
173
+ `Capsium::Package.new`/`Capsium::Reactor.new` through their new
174
+ `registry:` keyword) installs a dependency from the configured
175
+ registry when the store has no package for its GUID — fallback chain
176
+ store -> registry -> typed error (`DependencyNotFoundError` /
177
+ `UnsatisfiableDependencyError`).
178
+ - Reactor-level and per-package introspection endpoints (ARCHITECTURE.md
179
+ section 7 follow-ons), extending `Capsium::Reactor::Introspection`:
180
+ `GET /introspect/status` (`{status, uptime, packagesLoaded}`),
181
+ `GET /introspect/config` (port, store dir, cacheControl, authEnabled,
182
+ registry — deploy.json values and registry URL credentials are never
183
+ exposed) and `GET /introspect/metrics`
184
+ (`{uptime, requestsTotal, requestsByStatus}`) backed by the new
185
+ thread-safe in-memory `Capsium::Reactor::Metrics` counter wired into
186
+ the serving path, plus `GET /package/<name>/status`,
187
+ `GET /package/<name>/metadata` and `GET /package/<name>/logs` (the
188
+ reactor serves one package; any other name is a 404). `logs` returns
189
+ the last N lines (`?lines=N`, default 100) from the new
190
+ `Capsium::LogBuffer`, a small thread-safe ring buffer recording key
191
+ serving events (reactor start, package reload, one line per served
192
+ request). All endpoints are GET-only JSON (405 otherwise) and gated
193
+ when `authentication.json` enables authentication.
194
+
195
+ ### Changed
196
+
197
+ - `Metrics/ClassLength` rubocop budget raised to 150 for the reactor
198
+ and introspection classes grown by the phase-3 endpoints.
199
+
200
+ ## 0.3.0
201
+
202
+ ### Added
203
+
204
+ - Digital signatures (RSA-SHA256, X.509) per the packaging and security
205
+ standard clauses: `Capsium::Package::Signer` signs a package directory in
206
+ place (`signature.sig` plus the embedded public key PEM, recorded as
207
+ `security.digitalSignatures {publicKey, signatureFile}` in
208
+ `security.json`) and verifies declared signatures. The signed payload is
209
+ the concatenation, in sorted package-relative path order, of the raw
210
+ bytes of every checksum-covered file (everything except `security.json`
211
+ and `signature.sig`) — verifiable independently with
212
+ `openssl dgst -sha256 -verify`.
213
+ - Signed packages are verified automatically on load:
214
+ `Capsium::Package#verify_signature`/`#verify_signature!`/`#signed?`;
215
+ a mismatch raises `Capsium::Package::Signer::SignatureMismatchError`,
216
+ so the reactor refuses to serve tampered signed packages.
217
+ - `capsium package sign PATH --key KEY.pem [--cert CERT.pem]` and
218
+ `capsium package verify-signature PATH [--cert CERT.pem]` (exit status 1
219
+ on unsigned packages and verification failures). Both accept a package
220
+ directory or a `.cap` file.
221
+ - Whole-package encryption (AES-256-GCM, RSA-OAEP-SHA256 wrapped DEK) per
222
+ the packaging standard's encryption clause: `Capsium::Package::Cipher`
223
+ encrypts a package directory or `.cap` into the standard encrypted
224
+ layout (`metadata.json` cleartext, `signature.json` encryption envelope,
225
+ `package.enc` AES-256-GCM of the inner `.cap` zip) and decrypts it back.
226
+ The OCB/OpenPGP alternatives mentioned by the standard are intentionally
227
+ not implemented.
228
+ - `capsium package encrypt PATH --public-key PUB.pem -o OUT.cap` and
229
+ `capsium package decrypt PATH --private-key PRIV.pem [-o OUT.cap]`.
230
+ - `Capsium::Package.new(path, decryption_key:)` transparently decrypts an
231
+ encrypted `.cap` (or uncompressed encrypted directory) on load; without
232
+ a key it raises `Capsium::Package::Cipher::KeyRequiredError` (the
233
+ reactor refuses to serve), and a wrong key or tampered ciphertext raises
234
+ `Capsium::Package::Cipher::DecryptionError` (GCM authentication).
235
+ - `Capsium::Packager#transform_cap`/`#with_unpacked_cap` helpers for
236
+ in-place `.cap` transformations.
237
+ - The Capsium package testing YAML DSL (05x-testing): packages declare
238
+ tests in `tests/*.yaml` files (top-level `tests` list), and
239
+ `capsium package test PATH` runs them with per-test `PASS`/`FAIL`
240
+ output, a summary line and proper exit codes. Supported test types:
241
+ `route` (expected status, optional `response_contains` and
242
+ `expected_content_type` against a reactor started for the run),
243
+ `file` (existence, optional `contains`), `data_validation` (dataset
244
+ rows validated against a JSON schema, JSON and YAML formats) and
245
+ `config` (configuration files parsed and the known package configs
246
+ validated against their canonical models). Implemented as an
247
+ open/closed registry of `Capsium::Package::Testing::TestCase`
248
+ subclasses run by `Capsium::Package::Testing::TestSuite`; invalid
249
+ test definitions are reported as failures instead of aborting the run.
250
+ - Layered storage (ARCHITECTURE.md section 5a): `storage.layers` stacks
251
+ overlay directories over `content/` (bottom -> top in declaration
252
+ order), each mirroring the `content/` tree.
253
+ `Capsium::Package::MergedView` — the merged-view home shared by the
254
+ package (validation) and the reactor (serving) — resolves content
255
+ top-first, honors `.capsium-tombstones` deletions (a tombstoned path
256
+ resolves 404 even when a lower layer holds the file, and a tombstone
257
+ also suppresses dependency content below it), and hides
258
+ `visibility: private` layers and non-exported resources in the
259
+ exported-only view dependents get. Packages without a `layers` config
260
+ behave exactly as before (single implicit `content/` layer).
261
+ - Composite packages (ARCHITECTURE.md section 4a):
262
+ `metadata.dependencies` (GUID -> semver range) resolve against a
263
+ package store (`CAPSIUM_STORE` or `--store`) of
264
+ `<name>-<version>.cap` files plus an optional `index.json` (GUID ->
265
+ file); the newest satisfying version wins via the built-in semver
266
+ matcher `Capsium::Package::Version`/`VersionRange` (`>=`, `^`, `~`,
267
+ exact, `*`, `x`-wildcards/partials, comma/space conjunctions — no new
268
+ runtime dependency). Resolution is recursive with circularity
269
+ detection and raises typed errors:
270
+ `Capsium::Package::DependencyNotFoundError`,
271
+ `UnsatisfiableDependencyError`, `CircularDependencyError`,
272
+ `DependencyVisibilityError`.
273
+ - Resolved dependencies become read-only layers below all of the
274
+ dependent's own layers, and only `exported` resources are visible;
275
+ routes may address dependency content explicitly as
276
+ `"<dependency-guid>/<path>"` and declare the route-inheritance
277
+ attributes of 05x-routing: `remap` (replaces the serving path),
278
+ `responseRewrite` (`body`, `headers`), `responseHeaders` (merged over
279
+ served headers) and `requestHeaders` (parsed and exposed for
280
+ forwarding reactors; a documented no-op for this static reactor).
281
+ Referencing a dependency's private or missing resource is a load-time
282
+ error. `capsium package info` prints the resolved dependency tree;
283
+ `capsium reactor serve` accepts `--store`.
284
+ - Authentication (05x-authentication, ARCHITECTURE.md section 4b):
285
+ `authentication.json` declares `basicAuth {enabled, passwdFile,
286
+ realm}` and `oauth2 {enabled, provider, clientId, authorizationUrl,
287
+ tokenUrl, userinfoUrl, redirectPath, scopes}`; the reactor enforces
288
+ it via `Capsium::Reactor::Authenticator`. Basic authentication
289
+ challenges (401 + `WWW-Authenticate`) and verifies against the
290
+ htpasswd file — bcrypt via the new `bcrypt` runtime dependency, plus
291
+ pure-Ruby md5-crypt (`$apr1$`/`$1$` as deployed by
292
+ htpasswd/OpenSSL/glibc), `{SHA}` and a `crypt(3)` fallback.
293
+ OAuth2 runs the authorization-code flow over net/http: `/auth/login`
294
+ redirects with HMAC-signed state, the callback exchanges the code at
295
+ `tokenUrl`, fetches the userinfo claims and establishes an
296
+ HMAC-SHA256 signed session cookie (`Capsium::Reactor::Session`).
297
+ Secrets never come from the package: `clientSecret`/`sessionSecret`
298
+ and role assignments live in `deploy.json` (`--deploy` or
299
+ `CAPSIUM_DEPLOY`); without a configured session secret the reactor
300
+ generates one and persists it (mode 0600) outside the package.
301
+ Dataset-route `accessControl {roles, authenticationRequired}` is
302
+ enforced after authentication: 401 unauthenticated, 403 unauthorized.
303
+ - The `content-validity` introspection entry now reports `signed` and
304
+ `encrypted` status, plus `signatureValid` when the package declares a
305
+ signature; `Capsium::Package#encrypted?` reports whether the package
306
+ was loaded from an encrypted source.
307
+
308
+ ### Changed
309
+
310
+ - Integrity checksums now exclude `signature.sig` in addition to
311
+ `security.json` (the signature signs the checksum-covered payload, so it
312
+ cannot be part of it).
313
+ - `capsium package pack` drops signing artifacts (`signature.sig`,
314
+ embedded public key) when repacking: signing is a post-pack step.
315
+ - The reactor serves resources through `Package#merged_view` (the
316
+ overlay-aware resolution path); behavior is unchanged for packages
317
+ without layers.
318
+ - Loading a package with declared `metadata.dependencies` now resolves
319
+ them eagerly against the package store (`store:` or `CAPSIUM_STORE`);
320
+ a package whose dependencies cannot resolve (including normalized
321
+ pre-0.2 dependency declarations) fails to load with a typed error.
322
+ - `Metrics/ParameterLists` rubocop budget raised to 6 for the reactor
323
+ constructor's per-concern keywords.
324
+
3
325
  ## 0.2.0
4
326
 
5
327
  ### Changed