@rehearsal-db/core 0.1.0-rc.3 → 0.1.0-rc.7

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 (189) hide show
  1. package/BENCHMARKS.md +18 -0
  2. package/CHANGELOG.md +104 -2
  3. package/COMPATIBILITY.md +1 -1
  4. package/README.md +11 -0
  5. package/SECURITY.md +4 -3
  6. package/dist/package.json +12 -2
  7. package/dist/scripts/runtime/manage_database.mjs +12 -2
  8. package/dist/src/application/activity_contract.d.mts +28 -0
  9. package/dist/src/application/activity_contract.mjs +84 -0
  10. package/dist/src/application/activity_monitor.d.mts +20 -0
  11. package/dist/src/application/activity_monitor.mjs +191 -0
  12. package/dist/src/application/port_preflight.d.mts +12 -0
  13. package/dist/src/application/port_preflight.mjs +71 -0
  14. package/dist/src/application/process_group.d.mts +13 -0
  15. package/dist/src/application/process_group.mjs +55 -0
  16. package/dist/src/application/session.d.mts +11 -3
  17. package/dist/src/application/session.mjs +62 -20
  18. package/dist/src/baseline/artifact.d.mts +17 -1
  19. package/dist/src/baseline/artifact.mjs +91 -2
  20. package/dist/src/cli/application_command_handlers.mjs +1 -1
  21. package/dist/src/cli/arguments.d.mts +4 -0
  22. package/dist/src/cli/arguments.mjs +20 -0
  23. package/dist/src/cli/baseline_shape_rendering.d.mts +3 -0
  24. package/dist/src/cli/baseline_shape_rendering.mjs +32 -0
  25. package/dist/src/cli/basic_command_handlers.mjs +8 -1
  26. package/dist/src/cli/cli_commands.mjs +2 -0
  27. package/dist/src/cli/editor_command_handlers.d.mts +2 -0
  28. package/dist/src/cli/editor_command_handlers.mjs +60 -0
  29. package/dist/src/cli/identity_command_handlers.mjs +13 -9
  30. package/dist/src/cli/inspection_command_handlers.mjs +64 -0
  31. package/dist/src/cli/project_commands.d.mts +48 -0
  32. package/dist/src/cli/project_commands.mjs +11 -3
  33. package/dist/src/cli/rehearsal.mjs +59 -48
  34. package/dist/src/cli/renderers.d.mts +9 -0
  35. package/dist/src/cli/renderers.mjs +18 -0
  36. package/dist/src/cli/runtime_action_handlers.mjs +11 -7
  37. package/dist/src/cli/runtime_commands.d.mts +15 -159
  38. package/dist/src/cli/runtime_commands.mjs +143 -170
  39. package/dist/src/cli/runtime_failure_summary.d.mts +1 -0
  40. package/dist/src/cli/runtime_failure_summary.mjs +53 -0
  41. package/dist/src/cli/runtime_planning.d.mts +278 -0
  42. package/dist/src/cli/runtime_planning.mjs +130 -0
  43. package/dist/src/cli/runtime_stack_execution.d.mts +6 -0
  44. package/dist/src/cli/runtime_stack_execution.mjs +31 -0
  45. package/dist/src/cli/source_command_handlers.mjs +7 -0
  46. package/dist/src/cli/source_commands.d.mts +81 -0
  47. package/dist/src/cli/source_commands.mjs +38 -7
  48. package/dist/src/index.d.mts +3 -2
  49. package/dist/src/index.mjs +1 -2
  50. package/dist/src/project/configuration.d.mts +3 -1
  51. package/dist/src/project/configuration.mjs +1 -0
  52. package/dist/src/project/configuration_activity.d.mts +11 -0
  53. package/dist/src/project/configuration_activity.mjs +31 -0
  54. package/dist/src/project/configuration_application_urls.d.mts +10 -0
  55. package/dist/src/project/configuration_application_urls.mjs +18 -0
  56. package/dist/src/project/configuration_contract.d.mts +47 -0
  57. package/dist/src/project/configuration_contract.mjs +14 -1
  58. package/dist/src/project/configuration_normalization.d.mts +23 -0
  59. package/dist/src/project/configuration_normalization.mjs +60 -3
  60. package/dist/src/project/configuration_profiles.d.mts +12 -0
  61. package/dist/src/project/configuration_profiles.mjs +33 -0
  62. package/dist/src/project/configuration_rendering.mjs +45 -0
  63. package/dist/src/project/configuration_resolution.d.mts +24 -0
  64. package/dist/src/project/configuration_resolution.mjs +3 -0
  65. package/dist/src/project/configuration_resource_policy.d.mts +15 -0
  66. package/dist/src/project/configuration_resource_policy.mjs +65 -0
  67. package/dist/src/project/configuration_review.d.mts +46 -0
  68. package/dist/src/project/configuration_review.mjs +227 -0
  69. package/dist/src/project/configuration_schema.d.mts +2 -0
  70. package/dist/src/project/configuration_schema.mjs +28 -2
  71. package/dist/src/project/configuration_validation.mjs +4 -2
  72. package/dist/src/project/project_discovery.d.mts +15 -0
  73. package/dist/src/project/project_discovery.mjs +350 -0
  74. package/dist/src/project/project_discovery_application.d.mts +16 -0
  75. package/dist/src/project/project_discovery_application.mjs +190 -0
  76. package/dist/src/project/project_discovery_contract.d.mts +78 -0
  77. package/dist/src/project/project_discovery_contract.mjs +43 -0
  78. package/dist/src/project/project_discovery_metadata.d.mts +14 -0
  79. package/dist/src/project/project_discovery_metadata.mjs +138 -0
  80. package/dist/src/runtime/cleanup.d.mts +10 -13
  81. package/dist/src/runtime/cleanup.mjs +64 -77
  82. package/dist/src/runtime/configuration_binding.d.mts +6 -0
  83. package/dist/src/runtime/configuration_binding.mjs +10 -0
  84. package/dist/src/runtime/container_engine_ownership.d.mts +46 -0
  85. package/dist/src/runtime/container_engine_ownership.mjs +240 -0
  86. package/dist/src/runtime/environment_classification.d.mts +45 -0
  87. package/dist/src/runtime/environment_classification.mjs +83 -0
  88. package/dist/src/runtime/lifecycle_engine.d.mts +8 -1
  89. package/dist/src/runtime/lifecycle_engine.mjs +21 -2
  90. package/dist/src/runtime/local_docker_context.d.mts +27 -0
  91. package/dist/src/runtime/local_docker_context.mjs +92 -0
  92. package/dist/src/runtime/migration_plan.d.mts +52 -0
  93. package/dist/src/runtime/migration_plan.mjs +51 -0
  94. package/dist/src/runtime/plan.d.mts +142 -0
  95. package/dist/src/runtime/plan.mjs +75 -8
  96. package/dist/src/runtime/project_review.d.mts +90 -0
  97. package/dist/src/runtime/project_review.mjs +200 -0
  98. package/dist/src/runtime/resource_catalog.d.mts +80 -0
  99. package/dist/src/runtime/resource_catalog.mjs +170 -0
  100. package/dist/src/runtime/resource_images.d.mts +19 -0
  101. package/dist/src/runtime/resource_images.mjs +62 -0
  102. package/dist/src/runtime/resource_inventory.d.mts +93 -0
  103. package/dist/src/runtime/resource_inventory.mjs +509 -0
  104. package/dist/src/runtime/resource_policy.d.mts +38 -0
  105. package/dist/src/runtime/resource_policy.mjs +143 -0
  106. package/dist/src/runtime/resource_provenance.d.mts +31 -0
  107. package/dist/src/runtime/resource_provenance.mjs +143 -0
  108. package/dist/src/runtime/runtime_manager_guards.d.mts +11 -0
  109. package/dist/src/runtime/runtime_manager_guards.mjs +43 -0
  110. package/dist/src/runtime/runtime_startup_ownership.d.mts +36 -0
  111. package/dist/src/runtime/runtime_startup_ownership.mjs +221 -0
  112. package/dist/src/runtime/stale_environment_plan.d.mts +67 -0
  113. package/dist/src/runtime/stale_environment_plan.mjs +174 -0
  114. package/dist/src/runtime/stale_environment_stop.d.mts +12 -0
  115. package/dist/src/runtime/stale_environment_stop.mjs +98 -0
  116. package/dist/src/runtime/status.d.mts +2 -0
  117. package/dist/src/runtime/status.mjs +5 -0
  118. package/dist/src/runtime/topology.d.mts +2 -1
  119. package/dist/src/runtime/topology.mjs +17 -3
  120. package/dist/src/runtime/topology_application_ports.d.mts +6 -0
  121. package/dist/src/runtime/topology_application_ports.mjs +38 -0
  122. package/dist/src/shared/cancellation.mjs +7 -1
  123. package/dist/src/shared/diagnostics.mjs +4 -0
  124. package/dist/src/shared/operation_guard.d.mts +6 -0
  125. package/dist/src/shared/operation_guard.mjs +80 -14
  126. package/dist/src/shared/operation_progress.d.mts +13 -0
  127. package/dist/src/shared/operation_progress.mjs +163 -0
  128. package/dist/src/shared/operation_progress_contract.d.mts +46 -0
  129. package/dist/src/shared/operation_progress_contract.mjs +135 -0
  130. package/dist/src/shared/operation_progress_storage.d.mts +24 -0
  131. package/dist/src/shared/operation_progress_storage.mjs +149 -0
  132. package/dist/src/shared/owned_process.mjs +9 -0
  133. package/dist/src/source/baseline.d.mts +3 -1
  134. package/dist/src/source/baseline.mjs +99 -9
  135. package/dist/src/source/shape.d.mts +8 -0
  136. package/dist/src/source/shape.mjs +6 -0
  137. package/dist/src/source/shape_contract.d.mts +121 -0
  138. package/dist/src/source/shape_contract.mjs +2 -0
  139. package/dist/src/source/shape_evidence.d.mts +27 -0
  140. package/dist/src/source/shape_evidence.mjs +504 -0
  141. package/dist/src/source/shape_files.d.mts +9 -0
  142. package/dist/src/source/shape_files.mjs +30 -0
  143. package/dist/src/source/shape_json.d.mts +7 -0
  144. package/dist/src/source/shape_json.mjs +75 -0
  145. package/dist/src/source/shape_keys.d.mts +16 -0
  146. package/dist/src/source/shape_keys.mjs +84 -0
  147. package/dist/src/source/shape_policy.d.mts +4 -0
  148. package/dist/src/source/shape_policy.mjs +361 -0
  149. package/dist/src/source/shape_preparation.d.mts +20 -0
  150. package/dist/src/source/shape_preparation.mjs +94 -0
  151. package/dist/src/source/shape_profiles.d.mts +16 -0
  152. package/dist/src/source/shape_profiles.mjs +234 -0
  153. package/dist/src/source/shape_sampler.d.mts +14 -0
  154. package/dist/src/source/shape_sampler.mjs +175 -0
  155. package/dist/src/targets/docker_resource_ownership.d.mts +11 -0
  156. package/dist/src/targets/docker_resource_ownership.mjs +28 -0
  157. package/dist/src/targets/postgresql.mjs +35 -36
  158. package/dist/src/targets/postgresql_process.d.mts +10 -1
  159. package/dist/src/targets/postgresql_process.mjs +37 -4
  160. package/dist/src/targets/postgresql_status.d.mts +5 -2
  161. package/dist/src/targets/postgresql_status.mjs +14 -1
  162. package/dist/src/targets/supabase.d.mts +2 -7
  163. package/dist/src/targets/supabase.mjs +17 -13
  164. package/dist/src/targets/supabase_environment.d.mts +31 -3
  165. package/dist/src/targets/supabase_environment.mjs +205 -12
  166. package/dist/src/targets/supabase_process.d.mts +3 -1
  167. package/dist/src/targets/supabase_process.mjs +7 -4
  168. package/dist/src/targets/supabase_service_requirements.d.mts +6 -0
  169. package/dist/src/targets/supabase_service_requirements.mjs +42 -0
  170. package/dist/src/targets/supabase_start_failure.d.mts +6 -0
  171. package/dist/src/targets/supabase_start_failure.mjs +65 -0
  172. package/dist/src/targets/supabase_status.d.mts +4 -1
  173. package/dist/src/targets/supabase_status.mjs +11 -1
  174. package/docs/README.md +1 -0
  175. package/docs/architecture.md +14 -0
  176. package/docs/baselines.md +18 -0
  177. package/docs/commands.md +120 -13
  178. package/docs/configuration.md +252 -2
  179. package/docs/production-source.md +156 -0
  180. package/docs/releasing.md +116 -15
  181. package/docs/roadmap.md +6 -0
  182. package/docs/runtime-lifecycle.md +215 -8
  183. package/docs/security-model.md +4 -1
  184. package/docs/stability.md +4 -0
  185. package/docs/troubleshooting.md +29 -0
  186. package/docs/tutorial.md +19 -10
  187. package/docs/typescript.md +89 -6
  188. package/docs/vscode.md +54 -0
  189. package/package.json +12 -2
package/BENCHMARKS.md CHANGED
@@ -55,6 +55,24 @@ the same proof remains required in GitHub Actions before public release.
55
55
 
56
56
  ## Regression policy
57
57
 
58
+ The installed PostgreSQL scenario also demonstrates a data-specific failure:
59
+ the exact same `SET NOT NULL` migration succeeds on an empty table and fails on a
60
+ synthetic table containing a known null. It then reviews a changed candidate that
61
+ backfills the synthetic value, verifies the corrected constraint through the
62
+ application's HTTP proof, and checks immutable baselines and resource cleanup.
63
+ This is a correctness control, not a timing benchmark or a claim about production
64
+ traffic. The synthetic backfill is not a recommendation for real business data.
65
+
66
+ Maintainers can repeat it with one exact artifact:
67
+
68
+ ```bash
69
+ REHEARSAL_CANDIDATE_ARTIFACT=/absolute/path/to/candidate.tgz npm run test:fixture:postgresql
70
+ ```
71
+
72
+ The result records the artifact and candidate hashes, empty/populated outcomes,
73
+ baseline preservation and cleanup. Do not attribute a candidate's result to a
74
+ different registry artifact merely because both have the same version number.
75
+
58
76
  Record a new equivalent sample before a beta release or after changing baseline format,
59
77
  streaming, restore, migration application, verification, or local-runtime lifecycle.
60
78
  Investigate a repeatable increase greater than 25% in a phase. Do not weaken checks to
package/CHANGELOG.md CHANGED
@@ -3,7 +3,108 @@
3
3
  All notable Rehearsal package changes will be documented here. The format follows Keep
4
4
  a Changelog, and versions will follow Semantic Versioning after the package exists.
5
5
 
6
- ## Unreleased
6
+ ## [0.1.0-rc.7] - 2026-10-15
7
+
8
+ ### Fixed
9
+
10
+ - Release approval checks accept GitHub form line endings without changing the exact
11
+ owner, environment, operation, run, attempt, source or checksum bindings. Other text
12
+ changes and stale or ambiguous approvals remain refused.
13
+
14
+ rc.6's tag and failed GitHub publication history are preserved; it was not published
15
+ to npm. rc.7 does not add or change database, application or environment behavior.
16
+
17
+ ### Documentation
18
+
19
+ - The shared GitHub/npm README visibly links to both the source repository and npm
20
+ package page.
21
+
22
+ ## [0.1.0-rc.6] - 2026-10-15
23
+
24
+ ### Fixed
25
+
26
+ - Release ancestry verification no longer requests a paginated comparison when the
27
+ immutable source tag already points to the exact verified main commit. Older source
28
+ commits still require bounded, unpaged ancestry evidence; incomplete responses remain
29
+ refused. Human approval, provenance and immutable-tag protections are unchanged.
30
+
31
+ rc.5's GitHub candidate and tag are retained, but its npm publication stopped at the
32
+ read-only preflight. rc.6 carries the same product features and a new tested artifact;
33
+ no runtime or environment behavior was added during this publisher correction.
34
+
35
+ ## [0.1.0-rc.5] - 2026-10-15
36
+
37
+ ### Added
38
+
39
+ - Project-owned resource inventory, exact long-running-environment stop review, and
40
+ individually selected unused-image cleanup. Volumes remain tied to reviewed runtime
41
+ discard; other projects and the shared engine are protected.
42
+ - Friendly environment labels, protection, named display profiles, resource quotas and
43
+ review schedules, with validated configuration metadata and fingerprint-bound proposals.
44
+ - Ordered migration review, baseline/privacy/authentication summaries, and bounded
45
+ operation stages and recent receipts through public JSON contracts.
46
+ - Optional target-local resource provenance reports recorded baseline and migration
47
+ digests, restore-writer version, and configured ports without claiming fresh
48
+ verification, listening services or activity. Legacy receipts remain explicitly unknown.
49
+ - Environment classification separately reports ownership-proven creation, recorded
50
+ format/provider compatibility, and age-based review due status. Missing verification
51
+ does not mean a database was never created; differing writer versions do not imply
52
+ incompatible data. Configured sandbox volumes remain persistent after workload stop.
53
+ - Opt-in, bounded graph sampling and measured distribution goals, with immutable
54
+ post-sanitization fidelity evidence and required-goal refusal before activation.
55
+ - Lightweight Supabase service profiles and explicit required-service checks.
56
+ - Opt-in foreground sandbox idle shutdown using an explicit application-owned activity
57
+ contract. Unknown or busy activity preserves workloads; no background daemon is added.
58
+ - A disposable PostgreSQL regression demo showing an empty-database migration pass,
59
+ populated-data refusal, and verified recovery with a reviewed backfill.
60
+
61
+ ### Fixed
62
+
63
+ - Partial startup and interrupted work clean up only workloads proved owned by the
64
+ operation, preserving existing running environments, data and shared engines.
65
+ - Opted-in owned-engine shutdown checks for newly appeared workloads immediately before
66
+ stopping and preserves a replacement ownership receipt from a later operation.
67
+ - Application startup refuses occupied local ports without replacing another process.
68
+ - Application and profile ports are checked across the complete configured topology;
69
+ configuration proposals can explicitly bind a dependent edit to its parent graph.
70
+ - Application teardown retires its owned process group even after the launcher exits.
71
+ - Supabase startup diagnostics preserve safe service/health categories while withholding
72
+ logs, credentials, URLs and private paths. Unknown causes stay unknown.
73
+ - Public JSON retains strictly false privacy attestations without exposing credentials;
74
+ normalized shape policies can be safely validated repeatedly.
75
+ - Failed migrations retain their baseline/candidate phase instead of inferring it from
76
+ earlier progress; summaries omit Node source excerpts and provider SQL output.
77
+ - Release preparation, publication and tag repair now refuse missing or ambiguous live
78
+ repository/environment controls; prereleases preserve a stable `latest` version even
79
+ when that version has build metadata.
80
+ - Read-only release CI drift-checks an owner-observed settings record when GitHub hides
81
+ bypass metadata. Publication still requires an actual owner deployment review bound
82
+ to the exact run, attempt, source, artifact and settings-record checksums.
83
+
84
+ These changes belong to rc.5, not the existing rc.4 artifact. Scheduled review is
85
+ not unattended cleanup, and container age is not proof of user inactivity.
86
+
87
+ ## [0.1.0-rc.4] - 2026-10-11
88
+
89
+ ### Added
90
+
91
+ - `discover --json` exposes a versioned, read-only Project Profile for editor clients,
92
+ separating proven facts, evidence-backed suggestions, and decisions that remain with
93
+ the user.
94
+ - Project discovery reports bounded application, package-manager, migration, target,
95
+ runtime, baseline-retention, configuration, and local-address metadata without reading
96
+ environment files, contacting databases, starting containers, or executing project code.
97
+ - Public TypeScript contracts now cover discovery results, retained baseline generations,
98
+ application readiness, cleanup policy, and structured PostgreSQL and Supabase runtime
99
+ state consumed by the Rehearsal VS Code extension.
100
+
101
+ ### Changed
102
+
103
+ - Plans and status results expose the additional bounded metadata required for a
104
+ project-aware editor experience while retaining core ownership of all safety checks and
105
+ mutating operations.
106
+ - Documentation now includes the supported VS Code integration boundary, privacy model,
107
+ compatibility floor, and safe automatic-refresh behavior.
7
108
 
8
109
  ## [0.1.0-rc.3] - 2026-10-10
9
110
 
@@ -605,7 +706,8 @@ a Changelog, and versions will follow Semantic Versioning after the package exis
605
706
  publication uses short-lived trusted OIDC, and every release tag must already exist on
606
707
  protected `main`.
607
708
 
608
- [Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.3...HEAD
709
+ [Unreleased]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.4...HEAD
710
+ [0.1.0-rc.4]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.3...v0.1.0-rc.4
609
711
  [0.1.0-rc.3]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.2...v0.1.0-rc.3
610
712
  [0.1.0-rc.2]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-rc.1...v0.1.0-rc.2
611
713
  [0.1.0-rc.1]: https://github.com/Ddupasquier/rehearsal-db/compare/v0.1.0-beta.18...v0.1.0-rc.1
package/COMPATIBILITY.md CHANGED
@@ -17,7 +17,7 @@ A change to any of those requires either a compatible extension or a deliberate
17
17
  transition. Rehearsal must never silently reinterpret an old config, baseline, digest,
18
18
  or successful verification receipt.
19
19
 
20
- The first stable `1.0.0` requires external beta evidence, a support policy, and a settled
20
+ The first `1.0.0` release requires external beta evidence, a support policy, and a settled
21
21
  public API. Supporting additional database families, package managers, or operating
22
22
  systems is not implied by the 0.x contract.
23
23
 
package/README.md CHANGED
@@ -16,6 +16,7 @@
16
16
  <p align="center">
17
17
  <a href="https://ddupasquier.github.io/rehearsal-db/">Read the documentation</a> ·
18
18
  <a href="https://www.npmjs.com/package/@rehearsal-db/core">View on npm</a> ·
19
+ <a href="https://github.com/Ddupasquier/rehearsal-db">Source on GitHub</a> ·
19
20
  <a href="https://github.com/Ddupasquier/rehearsal-db/issues">Report a problem</a>
20
21
  </p>
21
22
 
@@ -129,6 +130,15 @@ When local disk space gets tight, choose **Clean up disk space** in the guide. R
129
130
  previews old baseline generations first and keeps runtime or shared-image removal
130
131
  explicit.
131
132
 
133
+ Run `npx rehearsal environments` for a read-only view of this project's owned runtimes,
134
+ container state, current owned-container CPU and memory, Docker resource totals, and
135
+ measurable Colima disk capacity. Rehearsal
136
+ warns before the disk becomes critical and refuses expensive startup without configured
137
+ headroom. PostgreSQL uses one container for SQL-only proofs; Supabase's default
138
+ `application` profile keeps application services while omitting development-only extras.
139
+ Optional idle shutdown can stop only a Colima boot Rehearsal proves it started, and only
140
+ after every container on the shared engine is already stopped.
141
+
132
142
  Press `Ctrl+Z` at any guided prompt to exit the whole session. Choose **Get help**, or run
133
143
  `npx rehearsal support`, to create a privacy-safe diagnostic report.
134
144
 
@@ -166,6 +176,7 @@ Start here:
166
176
 
167
177
  - [Documentation home](docs/index.md) — choose the shortest path for your goal
168
178
  - [Getting started](docs/getting-started.md) — set up your own project
179
+ - [VS Code extension](docs/vscode.md) — use the project-aware visual workbench
169
180
  - [Safe hands-on tutorial](docs/tutorial.md) — try the full flow in a disposable project
170
181
  - [Troubleshooting](docs/troubleshooting.md) — fix common setup problems
171
182
  - [Next steps](docs/roadmap.md) — see the current product sequence
package/SECURITY.md CHANGED
@@ -4,9 +4,10 @@ Rehearsal handles sensitive database topology and may process sanitized derivati
4
4
  production data. Do not include credentials, connection strings, raw data, or a real
5
5
  baseline in a public report.
6
6
 
7
- Report suspected vulnerabilities through GitHub's private vulnerability reporting for
8
- this repository. Do not open a public issue containing an exploit, secret, hosted
9
- target, or source record.
7
+ Report suspected vulnerabilities through the
8
+ [private security form](https://github.com/Ddupasquier/rehearsal-db/security/advisories/new)
9
+ in this repository. A GitHub account is required. Do not open a
10
+ public issue containing an exploit, secret, hosted target, or source record.
10
11
 
11
12
  Supported security updates cover the latest released 0.x minor during beta. A security
12
13
  fix may deliberately tighten configuration or refuse a workflow that an earlier beta
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rehearsal-db/core",
3
- "version": "0.1.0-rc.3",
3
+ "version": "0.1.0-rc.7",
4
4
  "private": false,
5
5
  "description": "Catch PostgreSQL and Supabase migrations that pass on empty databases but fail on real data—before deploy.",
6
6
  "repository": {
@@ -22,6 +22,14 @@
22
22
  "bin": {
23
23
  "rehearsal": "dist/src/cli/rehearsal.mjs"
24
24
  },
25
+ "rehearsalContracts": {
26
+ "projectDiscovery": 1,
27
+ "resourceInventory": 1,
28
+ "resourceManager": 1,
29
+ "migrationReview": 1,
30
+ "configurationReview": 1,
31
+ "baselineShape": 1
32
+ },
25
33
  "exports": {
26
34
  ".": {
27
35
  "types": "./types/configuration.d.mts",
@@ -35,7 +43,8 @@
35
43
  "./privacy": "./dist/src/baseline/privacy_engine.mjs",
36
44
  "./schema": "./dist/src/baseline/schema_snapshot.mjs",
37
45
  "./service-environment": "./dist/src/runtime/service_environment.mjs",
38
- "./source-access": "./dist/src/source/access.mjs"
46
+ "./source-access": "./dist/src/source/access.mjs",
47
+ "./shape": "./dist/src/source/shape.mjs"
39
48
  },
40
49
  "files": [
41
50
  "dist",
@@ -66,6 +75,7 @@
66
75
  "test:fixture:typescript": "npm run build && node scripts/verification/run_scenario.mjs typescript",
67
76
  "test:fixture:standalone": "npm run build && node scripts/verification/run_scenario.mjs standalone",
68
77
  "test:fixture:privacy": "npm run build && node scripts/verification/run_scenario.mjs privacy",
78
+ "test:fixture:resources": "npm run build && node scripts/verification/run_scenario.mjs resources",
69
79
  "release:verify": "node scripts/verification/release_gate.mjs",
70
80
  "check": "npm run check:syntax && npm run typecheck && npm run test && npm run docs:build && npm run format:check && npm run package:audit",
71
81
  "check:syntax": "find scripts tests -type f -name '*.mjs' -exec node --check {} +",
@@ -7,15 +7,25 @@ import { loadRehearsalConfig } from "../../src/project/configuration.mjs";
7
7
  import { parseRuntimeInvocation } from "../../src/targets/target.mjs";
8
8
  import { runRuntimeTarget } from "../../src/targets/driver.mjs";
9
9
  import { runWithOperationCancellation } from "../../src/shared/cancellation.mjs";
10
+ import { withRuntimeStartupCleanup } from "../../src/runtime/runtime_startup_ownership.mjs";
11
+ import { guardRuntimeManagerAction } from "../../src/runtime/runtime_manager_guards.mjs";
10
12
  const run = async () => {
11
13
  const invocation = parseRuntimeInvocation();
12
- const { config } = await loadRehearsalConfig({
14
+ const loaded = await loadRehearsalConfig({
13
15
  projectRoot: process.cwd(),
14
16
  ...(invocation.configPath === undefined
15
17
  ? {}
16
18
  : { configPath: invocation.configPath }),
17
19
  });
18
- await runRuntimeTarget(config.runtime.target);
20
+ await guardRuntimeManagerAction(loaded, invocation.action);
21
+ await withRuntimeStartupCleanup({
22
+ projectRoot: loaded.projectRoot,
23
+ configPath: loaded.configPath,
24
+ config: loaded.config,
25
+ action: invocation.action,
26
+ ownerOperationId: process.env.REHEARSAL_OWNER_OPERATION_ID,
27
+ task: () => runRuntimeTarget(loaded.config.runtime.target),
28
+ });
19
29
  };
20
30
  if (process.env.REHEARSAL_CANCELLABLE_OPERATION === "1") {
21
31
  await runWithOperationCancellation({ task: run });
@@ -0,0 +1,28 @@
1
+ /** Explicit application-owned activity; never infer use from container or editor age. */
2
+ export interface ApplicationActivity {
3
+ readonly url: string;
4
+ }
5
+ export interface ApplicationActivitySample {
6
+ readonly schemaVersion: 1;
7
+ readonly busy: boolean;
8
+ readonly activitySequence: number;
9
+ }
10
+ export interface ApplicationIdleWindow {
11
+ readonly sequence: number | null;
12
+ readonly quietSince: number | null;
13
+ readonly lastObservedAt: number | null;
14
+ readonly validSamples: number;
15
+ }
16
+ export declare const emptyApplicationIdleWindow: () => ApplicationIdleWindow;
17
+ export declare const validateApplicationActivityUrl: (value: unknown, readinessUrl: string | null) => string;
18
+ export declare const parseApplicationActivitySample: (value: unknown) => ApplicationActivitySample | null;
19
+ /** `now` is a monotonic clock; missing evidence cannot carry a previous quiet window. */
20
+ export declare const observeApplicationActivity: ({ previous, sample, now, timeoutMs, }: {
21
+ readonly previous: ApplicationIdleWindow;
22
+ readonly sample: ApplicationActivitySample | null;
23
+ readonly now: number;
24
+ readonly timeoutMs: number;
25
+ }) => {
26
+ readonly window: ApplicationIdleWindow;
27
+ readonly idle: boolean;
28
+ };
@@ -0,0 +1,84 @@
1
+ export const emptyApplicationIdleWindow = () => ({
2
+ sequence: null,
3
+ quietSince: null,
4
+ lastObservedAt: null,
5
+ validSamples: 0,
6
+ });
7
+ export const validateApplicationActivityUrl = (value, readinessUrl) => {
8
+ const message = "Application activity requires a bounded credential-free loopback HTTP(S) URL on the exact readiness origin, with an explicit non-privileged port and no query or fragment.";
9
+ if (typeof value !== "string" ||
10
+ value.length > 500 ||
11
+ /[\x00-\x20\x7f]/u.test(value))
12
+ throw new Error(message);
13
+ let url;
14
+ let readiness;
15
+ try {
16
+ url = new URL(value);
17
+ readiness = new URL(readinessUrl ?? "");
18
+ }
19
+ catch {
20
+ throw new Error(message);
21
+ }
22
+ if (!/^https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):[0-9]+(?:\/|$)/iu.test(value) ||
23
+ !["http:", "https:"].includes(url.protocol) ||
24
+ !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname) ||
25
+ url.username ||
26
+ url.password ||
27
+ url.search ||
28
+ url.hash ||
29
+ Number(url.port) < 1024 ||
30
+ Number(url.port) > 65535 ||
31
+ readiness.username ||
32
+ readiness.password ||
33
+ url.origin !== readiness.origin)
34
+ throw new Error(message);
35
+ return url.href;
36
+ };
37
+ export const parseApplicationActivitySample = (value) => {
38
+ if (!value || typeof value !== "object" || Array.isArray(value))
39
+ return null;
40
+ const sample = value;
41
+ if (Object.keys(sample).length !== 3 ||
42
+ Object.keys(sample).some((key) => !["schemaVersion", "busy", "activitySequence"].includes(key)) ||
43
+ sample.schemaVersion !== 1 ||
44
+ typeof sample.busy !== "boolean" ||
45
+ typeof sample.activitySequence !== "number" ||
46
+ !Number.isSafeInteger(sample.activitySequence) ||
47
+ sample.activitySequence < 0)
48
+ return null;
49
+ return {
50
+ schemaVersion: 1,
51
+ busy: sample.busy,
52
+ activitySequence: sample.activitySequence,
53
+ };
54
+ };
55
+ /** `now` is a monotonic clock; missing evidence cannot carry a previous quiet window. */
56
+ export const observeApplicationActivity = ({ previous, sample, now, timeoutMs, }) => {
57
+ if (!Number.isFinite(now) ||
58
+ !Number.isFinite(timeoutMs) ||
59
+ timeoutMs <= 0 ||
60
+ (previous.lastObservedAt !== null && now < previous.lastObservedAt) ||
61
+ !sample ||
62
+ (previous.sequence !== null && sample.activitySequence < previous.sequence))
63
+ return {
64
+ window: { ...previous, quietSince: null, validSamples: 0 },
65
+ idle: false,
66
+ };
67
+ const changed = previous.sequence === null || sample.activitySequence > previous.sequence;
68
+ const reset = changed || sample.busy || previous.quietSince === null;
69
+ const quietSince = sample.busy ? null : reset ? now : previous.quietSince;
70
+ const validSamples = sample.busy
71
+ ? 0
72
+ : reset
73
+ ? 1
74
+ : Math.min(previous.validSamples + 1, 2);
75
+ return {
76
+ window: {
77
+ sequence: sample.activitySequence,
78
+ quietSince,
79
+ validSamples,
80
+ lastObservedAt: now,
81
+ },
82
+ idle: quietSince !== null && validSamples >= 2 && now - quietSince >= timeoutMs,
83
+ };
84
+ };
@@ -0,0 +1,20 @@
1
+ import { type ApplicationActivitySample } from "./activity_contract.mjs";
2
+ export declare const readApplicationActivity: ({ url, signal, fetchImplementation, timeoutMs, }: {
3
+ readonly url: string;
4
+ readonly signal: AbortSignal;
5
+ readonly fetchImplementation?: typeof fetch;
6
+ readonly timeoutMs?: number;
7
+ }) => Promise<ApplicationActivitySample | null>;
8
+ export declare const waitForApplicationIdle: ({ url, readinessUrl, timeoutSeconds, signal, fetchImplementation, now, pollIntervalMs, requestTimeoutMs, onAvailability, }: {
9
+ readonly url: string;
10
+ readonly readinessUrl: string;
11
+ readonly timeoutSeconds: number;
12
+ readonly signal: AbortSignal;
13
+ readonly fetchImplementation?: typeof fetch;
14
+ readonly now?: () => number;
15
+ readonly pollIntervalMs?: number;
16
+ readonly requestTimeoutMs?: number;
17
+ readonly onAvailability?: (available: boolean) => void;
18
+ }) => Promise<Readonly<{
19
+ reason: "idle-timeout";
20
+ }>>;
@@ -0,0 +1,191 @@
1
+ /** Foreground-only serial polling of an explicitly declared application activity route. */
2
+ import { performance } from "node:perf_hooks";
3
+ import { emptyApplicationIdleWindow, observeApplicationActivity, parseApplicationActivitySample, validateApplicationActivityUrl, } from "./activity_contract.mjs";
4
+ const MAXIMUM_ACTIVITY_BYTES = 4096;
5
+ const aborted = () => Object.assign(new Error("Application activity monitoring stopped."), {
6
+ name: "AbortError",
7
+ });
8
+ class ActivityTransportUnresponsive extends Error {
9
+ constructor() {
10
+ super("Application activity transport did not stop within its bound; idle shutdown is disabled for this foreground session.");
11
+ }
12
+ }
13
+ const settleWithin = async (promise, milliseconds) => {
14
+ let timer;
15
+ try {
16
+ return await Promise.race([
17
+ promise.then(() => true, () => true),
18
+ new Promise((resolve) => {
19
+ timer = setTimeout(() => resolve(false), milliseconds);
20
+ }),
21
+ ]);
22
+ }
23
+ finally {
24
+ if (timer)
25
+ clearTimeout(timer);
26
+ }
27
+ };
28
+ const wait = (milliseconds, signal) => new Promise((resolve, reject) => {
29
+ if (signal.aborted) {
30
+ reject(aborted());
31
+ return;
32
+ }
33
+ const finish = (cancelled) => {
34
+ clearTimeout(timer);
35
+ signal.removeEventListener("abort", onAbort);
36
+ if (cancelled)
37
+ reject(aborted());
38
+ else
39
+ resolve();
40
+ };
41
+ const onAbort = () => finish(true);
42
+ const timer = setTimeout(() => finish(false), milliseconds);
43
+ signal.addEventListener("abort", onAbort, { once: true });
44
+ });
45
+ export const readApplicationActivity = async ({ url, signal, fetchImplementation = fetch, timeoutMs = 2000, }) => {
46
+ if (signal.aborted)
47
+ throw aborted();
48
+ const started = performance.now();
49
+ const budget = Math.min(Math.max(timeoutMs, 1), 2000);
50
+ const request = new AbortController();
51
+ let rejectOwner = () => undefined;
52
+ const ownerAborted = new Promise((_resolve, reject) => {
53
+ rejectOwner = reject;
54
+ });
55
+ const onAbort = () => {
56
+ request.abort();
57
+ rejectOwner(aborted());
58
+ };
59
+ signal.addEventListener("abort", onAbort, { once: true });
60
+ const abortTimer = setTimeout(() => request.abort(), Math.max(1, budget - 50));
61
+ let deadlineTimer;
62
+ const deadline = new Promise((_resolve, reject) => {
63
+ deadlineTimer = setTimeout(() => {
64
+ request.abort();
65
+ reject(new ActivityTransportUnresponsive());
66
+ }, budget);
67
+ });
68
+ let reader;
69
+ const read = async () => {
70
+ try {
71
+ const response = await fetchImplementation(url, {
72
+ signal: request.signal,
73
+ method: "GET",
74
+ redirect: "error",
75
+ headers: { accept: "application/json", "cache-control": "no-store" },
76
+ });
77
+ if (request.signal.aborted)
78
+ return null;
79
+ if (response.status !== 200 ||
80
+ response.redirected ||
81
+ !/^application\/json(?:\s*;|$)/iu.test(response.headers.get("content-type") ?? "") ||
82
+ !response.body)
83
+ return null;
84
+ const declared = response.headers.get("content-length");
85
+ if (declared !== null &&
86
+ (!/^[0-9]+$/u.test(declared) ||
87
+ Number(declared) > MAXIMUM_ACTIVITY_BYTES))
88
+ return null;
89
+ reader = response.body.getReader();
90
+ const chunks = [];
91
+ let length = 0;
92
+ while (true) {
93
+ if (request.signal.aborted)
94
+ return null;
95
+ const result = await reader.read();
96
+ if (request.signal.aborted)
97
+ return null;
98
+ if (result.done)
99
+ break;
100
+ length += result.value.byteLength;
101
+ if (length > MAXIMUM_ACTIVITY_BYTES)
102
+ return null;
103
+ chunks.push(result.value);
104
+ }
105
+ const bytes = Buffer.concat(chunks, length);
106
+ return parseApplicationActivitySample(JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(bytes)));
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ };
112
+ try {
113
+ return await Promise.race([read(), deadline, ownerAborted]);
114
+ }
115
+ finally {
116
+ clearTimeout(abortTimer);
117
+ if (deadlineTimer)
118
+ clearTimeout(deadlineTimer);
119
+ signal.removeEventListener("abort", onAbort);
120
+ request.abort();
121
+ const cancelled = reader?.cancel().catch(() => undefined);
122
+ const cancellationFinished = cancelled === undefined ||
123
+ (await settleWithin(cancelled, Math.max(1, Math.min(50, budget - (performance.now() - started)))));
124
+ try {
125
+ reader?.releaseLock();
126
+ }
127
+ catch {
128
+ /* A stuck transport is disabled below. */
129
+ }
130
+ if (signal.aborted)
131
+ throw aborted();
132
+ if (!cancellationFinished)
133
+ throw new ActivityTransportUnresponsive();
134
+ }
135
+ };
136
+ export const waitForApplicationIdle = async ({ url, readinessUrl, timeoutSeconds, signal, fetchImplementation = fetch, now = () => performance.now(), pollIntervalMs = 5000, requestTimeoutMs = 2000, onAvailability = () => undefined, }) => {
137
+ const endpoint = validateApplicationActivityUrl(url, readinessUrl);
138
+ if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0)
139
+ throw new Error("Application idle monitoring requires an explicit positive timeout.");
140
+ let window = emptyApplicationIdleWindow();
141
+ let available;
142
+ while (!signal.aborted) {
143
+ let sample;
144
+ try {
145
+ sample = await readApplicationActivity({
146
+ url: endpoint,
147
+ signal,
148
+ fetchImplementation,
149
+ timeoutMs: requestTimeoutMs,
150
+ });
151
+ }
152
+ catch (error) {
153
+ if (signal.aborted)
154
+ throw aborted();
155
+ if (!(error instanceof ActivityTransportUnresponsive))
156
+ throw error;
157
+ onAvailability(false);
158
+ // Do not accumulate new requests behind a transport that ignored abort/cancel.
159
+ await new Promise((_resolve, reject) => {
160
+ const stop = () => reject(aborted());
161
+ signal.addEventListener("abort", stop, { once: true });
162
+ if (signal.aborted) {
163
+ signal.removeEventListener("abort", stop);
164
+ stop();
165
+ }
166
+ });
167
+ throw aborted();
168
+ }
169
+ if (signal.aborted)
170
+ throw aborted();
171
+ const current = observeApplicationActivity({
172
+ previous: window,
173
+ sample,
174
+ now: now(),
175
+ timeoutMs: timeoutSeconds * 1000,
176
+ });
177
+ window = current.window;
178
+ const nextAvailable = sample !== null &&
179
+ (window.sequence === null ||
180
+ sample.activitySequence >= window.sequence) &&
181
+ window.lastObservedAt !== null;
182
+ if (available !== nextAvailable) {
183
+ available = nextAvailable;
184
+ onAvailability(available);
185
+ }
186
+ if (current.idle)
187
+ return { reason: "idle-timeout" };
188
+ await wait(Math.max(1, pollIntervalMs), signal);
189
+ }
190
+ throw aborted();
191
+ };
@@ -0,0 +1,12 @@
1
+ export interface LocalApplicationEndpoint {
2
+ readonly host: "localhost" | "127.0.0.1" | "::1";
3
+ readonly port: number;
4
+ }
5
+ type ProbeState = "free" | "occupied" | "unavailable" | "unsupported";
6
+ export type ApplicationPortProbe = (host: "127.0.0.1" | "::1", port: number) => Promise<ProbeState>;
7
+ export declare const parseLocalApplicationEndpoint: (value: unknown) => LocalApplicationEndpoint;
8
+ export declare const assertApplicationPortAvailable: ({ url, probe, }: {
9
+ readonly url: unknown;
10
+ readonly probe?: ApplicationPortProbe;
11
+ }) => Promise<void>;
12
+ export {};