Repository architecture#
Rehearsal separates shipped product code, repository automation, tests, fixtures, and documentation so each file has one obvious home.
rehearsal-db/
├── src/ TypeScript product source
│ ├── application/ application lifecycle and HTTP proofs
│ ├── baseline/ sanitized baseline creation and validation
│ ├── cli/ the `rehearsal` executable
│ ├── identity/ local account and Storage association
│ ├── project/ configuration, setup, and support
│ ├── runtime/ plans and disposable-runtime lifecycle
│ ├── shared/ small target-neutral utilities
│ ├── source/ approved source access and refresh
│ └── targets/ PostgreSQL and Supabase adapters
├── scripts/
│ ├── build/ clean TypeScript compilation helpers
│ ├── install/ TypeScript install-hook source
│ ├── runtime/ TypeScript runtime-launcher source
│ └── verification/ repository-only package and fixture proofs
├── dist/ generated ESM and declaration output; never edit directly
├── types/ compatibility entry point for generated declarations
├── tests/
│ ├── unit/ mirrors the `src/` domains
│ ├── integration/ multi-module and terminal behavior
│ ├── contracts/ public documentation and package promises
│ └── fixtures/ disposable example projects
└── docs/ user and maintainer documentationBoundaries#
src/must never import from tests or repository-only verification scripts.dist/is rebuilt from TypeScript before tests and packaging; never hand-edit it.- Database-specific lifecycle behavior belongs in
src/targets/. scripts/verification/is never included in the npm package.- Public consumers use only the paths declared in
package.json#exports. - Unit tests mirror their source domain. Cross-domain workflows belong in
tests/integration/; durable public promises belong intests/contracts/.
CLI modules#
The executable is split by responsibility so no command file becomes a second application layer. Scripted commands and the interactive guide resolve through the same typed registry and produce the same result contract:
rehearsal.mtsowns process lifecycle, project locking, guided-loop coordination, and top-level error handling. It does not implement commands.arguments.mtsowns the command-line contract and mutation classification.command_contract.mts,command_registry.mts, andcommand_output.mtsdefine command context, handler selection, stable JSON envelopes, human rendering, and exit codes.*_command_handlers.mtsandruntime_action_handlers.mtsgroup handlers by project, source, identity, inspection, application, or runtime responsibility.cli_commands.mtscomposes those focused families in precedence order without implementing their behavior.guided.mtsowns the interactive home screen and first-run questions.guided_updates.mtsowns optional update consent and reminders in that guide;update_advisory.mtsbounds public release metadata, private preferences and installation-origin checks. Neither participates in passive JSON inspection.runtime_commands.mtscoordinates local runtimes and project proofs.runtime_planning.mtsowns read-only stack plans and exact candidate summaries.editor_command_handlers.mtsexposes bounded migration and configuration review contracts plus digest-bound, non-destructive stale-environment stop review.source_commands.mtscoordinates approved source access and refresh.renderers.mtsformats human-readable results without changing state.terminal.mtsowns prompts, colors, and terminal exit behavior.
Command handlers receive an explicit context containing parsed flags, selected config,
guided state, and session-local caches. They return a rendered, text, or silent result;
only the shared output boundary creates JSON envelopes or writes final human output.
runtime/migration_failure.mts owns bounded provider-envelope SQLSTATE recognition,
fixed classification/remediation and closed safe diagnostic validation. Target drivers
never forward SQL or provider error details. The manager summary reconstructs that
fixed diagnostic across the child-process boundary; CLI JSON and private operation
history expose the same projection. Activity adds a separate recentFailures list,
leaving existing strict progress/recent receipt shapes unchanged for old clients.
The optional source projection is owned by runtime/migration_failure_source.mts.
For individually dispatched PostgreSQL files, it identifies the attempted execution
input by its reviewed filename, exact SHA-256 and baseline/candidate kind. The driver
hashes the bytes it loads before dispatch; changed reviewed bytes refuse execution.
Only a safe project-relative candidate path may be included, never an absolute path;
baseline inputs keep that path null. This is not attribution of the failing SQL's actual
origin: included scripts, functions and triggers can fail elsewhere. SQL line and column
remain null. Supabase batch execution currently supplies no file attribution, and
ambiguous or unavailable evidence is not inferred from candidate count or provider text.
cli/failure_output.mts preserves the outer category and cleanup warning when a
stack failure wraps the original migration error. It projects only a validated fixed
diagnosis from at most four original-error wrappers, never cleanup siblings or causes.
Session caches never appear in the public parsed-flags type.
Owned child output has a byte boundary. If a process floods it, Rehearsal sends TERM
and forcibly reaps only that owned process group after one second; a child ignoring
TERM cannot leave the operation hanging indefinitely.
Repository contracts keep the process entry point at 300 lines or fewer and every handler-family module at 350 lines or fewer. A new command belongs in the narrowest existing family, or in a new focused family when it represents a genuinely separate responsibility.
Configuration pipeline#
The stable project/configuration.mts facade keeps existing imports compatible while
configuration work moves through explicit phases:
configuration_discovery.mtsselects one supported project-owned config path.configuration_schema.mtsvalidates the raw object shape and rejects unknown keys.configuration_normalization.mtsapplies defaults and semantic safety constraints.configuration_resolution.mtsresolves owned paths from the project root and checks destructive-operation boundaries.
The project_discovery.mts facade orchestrates the separate read-only Project Profile.
Its versioned contract, bounded metadata access, and application inference live in
focused project_discovery_* modules. They never import project modules and classify
output as detected, suggested, or requiring a human decision. Discovery may report
proposed configuration fields, but setup and configuration rendering remain the only
owners that can prepare writes. Editor clients consume rehearsal discover --json;
they do not reproduce comprehensive discovery rules.
The public contract/defaults, primitive validators, project detection, and starter
rendering have their own focused modules. NormalizedRehearsalConfig is inferred from
the normalizer, while the package's public declarations are emitted from the same
TypeScript implementation. Repository contracts keep the facade at 50 lines or fewer
and each configuration phase at 700 lines or fewer.
Installed-consumer verification#
Repository verification installs the package tarball into disposable projects; it must
not import unpublished product source by accident. One scenario catalog drives both the
individual fixture commands and npm run release:verify.
scenarios/artifact.mjsselects or packs one tarball, reads its package identity, and records its SHA-256 before installation.scenarios/workspace.mjscreates and removes only scenario-owned scratch roots.scenarios/process.mjsstarts commands with inherited credentials removed unless a scenario explicitly supplies a safe value.scenarios/application_session.mjsowns persistent application and terminal-session checks.scenarios/fixture_container.mjsowns only the standalone source fixture: a unique creation ID and nonce, pinned local engine, and image-created anonymous volumes. Cleanup refuses ownership or mount drift, verifies removal, and cannot hide the original failure or report success early. This is not persistent-runtime cleanup.scenarios/catalog.mjsis the sole ordered list of installed-consumer scenarios.- Fixture files contain only the target setup and assertions unique to that scenario.
Each scenario remains independently runnable through its test:fixture:* script. The
complete release gate runs every catalog entry against the same immutable tarball and
writes its version, SHA-256, tool environment, commands, timings, and results under
test-results/release-gate/.
The existing Supabase scenario includes native syntax and populated-row constraint
failures, fixed public error/history diagnostics, reviewed corrections and independent
data/Storage preservation checks. It does not infer a rollback from a failed migration.
Runtime lifecycle#
runtime/lifecycle_engine.mts owns the target-neutral lifecycle contract. Both
PostgreSQL and Supabase use it to dispatch commands, verify active baseline inputs,
calculate and confirm the exact migration set, write target-bound receipts, validate
runtime markers, and attempt owned failure cleanup without hiding the original error.
That cleanup can remove the disposable runtime; a failure message does not establish a
SQL transaction rollback, restoration of sandbox edits, or successful resource removal.
The files in src/targets/ remain drivers. They prove ownership before deleting local
resources and implement only the behavior that genuinely differs: Docker PostgreSQL
containers and SQL setup on one side; Supabase CLI, Auth, Storage, and generated config
on the other. An ordinary stop never enters rollback or discard, so later start
preserves the sandbox database and Storage contents.
CLI command completion and database lifecycle are separate. A successful run can finish
and release its project lock while the configured keep-until-stop database remains
running. The project-owned CLI proof has its own primary application-proof stage;
HTTP readiness is optional and is not a prerequisite for a database-only proof. Public
operation receipts record those past stages, not fresh proof of subsequently changed
configuration, baseline or migration bytes. Editor presentation consumes that evidence
without interpreting database liveness as an active command or successful proof.
Repository contracts keep each target driver at 900 lines or fewer and the shared lifecycle engine at 300 lines or fewer. A new database target must use this lifecycle boundary rather than copying an existing driver's orchestration.
Identity claim pipeline#
The stable identity/claim.mts facade exposes policy validation, deterministic planning,
and confirmed execution while keeping those responsibilities separate:
claim_contract.mtsdefines the normalized policy, plan, database, and receipt types.claim_policy.mtsvalidates and normalizes the reviewed declaration.claim_plan.mtsbinds the local matcher value to an immutable redacted review and digest without opening a database or Storage connection.claim_sql.mtsgenerates identifier-safe relational, JSON, signup-default, and path SQL without executing it.claim_identity_lookup.mtsperforms read-only local Auth and reference discovery.claim_verification.mtschecks signup defaults and token-hook claim shapes.claim_executor.mtsalone owns the database transaction, Storage staging, rollback, commit, old-object cleanup, and final receipt.connect.mtsperforms read-only, provider-neutral sign-in discovery and produces the redacted preview used by the interactive copied-account connection.
Repository contracts keep the facade at 50 lines or fewer and every identity-claim stage at 600 lines or fewer. Provider callbacks and consumer application behavior stay outside this pipeline.
Privacy rule pipeline#
The stable baseline/privacy_engine.mts facade preserves the documented privacy import
path while policy work is divided into fail-closed typed stages:
privacy_contract.mtsdefines normalized policy and execution types.privacy_recipe_validation.mtsnormalizes only reviewed rule shapes;privacy_policy.mtsvalidates complete policy structure.privacy_dependency_graph.mtschecks owner bindings, path mappings, sanitized digest inputs, cycles, and shared date groups.privacy_transforms.mtsimplements deterministic pseudonyms and bounded paths.privacy_rule_registry.mtsexplicitly registers every action and derivation kind; unknown kinds have no fallback execution path.privacy_structured_json.mtsowns bounded object, array, union, and dictionary rules.privacy_pipeline.mtsresolves reviewed bindings and executes rows, whileprivacy_key.mtsowns the local secret-file boundary.
Repository contracts keep the facade at 50 lines or fewer and every privacy stage at 700 lines or fewer. Adding a rule requires its normalized type, validator, registered handler, focused boundary tests, and documentation—without widening policy version 2.
Adding something new#
- Put policy and reusable behavior in the domain that owns it.
- Put target-specific implementation behind the target boundary.
- Add its focused test under the matching
tests/unit/domain. - Add an integration or contract test only when behavior crosses domains or becomes a public promise.
- Update the documentation map when adding a new guide.
Run npm run build after changing TypeScript source. Tests import the generated package
code so the same JavaScript boundary consumers install is exercised locally.