## ADDED Requirements ### Requirement: Garage v2 image policy The `@suaveplan/testing-integration-garage-s3` plugin SHALL pin to `dxflrs/garage:v2.0.0` (name + minor version). A follow-up operational change tracked at `docs/superpowers/specs/2026-04-25-test-suite-audit.md §5` will pin to `sha256:`. #### Scenario: Image tag pinned - **WHEN** the container factory is invoked without injection - **THEN** the resulting container uses image `dxflrs/garage:v2.0.0` ### Requirement: Dual-port exposure The fixture SHALL expose two TCP ports from the container: the S3 API port on container port `3900` and the admin port on container port `3902`. Both MUST be mapped to host ports and recorded in `config.s3Endpoint` and `config.adminEndpoint`. #### Scenario: Both endpoints recorded - **WHEN** the container has started - **THEN** `config.s3Endpoint` and `config.adminEndpoint` are populated with reachable host URLs ### Requirement: Env-bridge contract The plugin SHALL declare and forward exactly four environment variables: `GARAGE_S3_ENDPOINT`, `GARAGE_S3_ACCESS_KEY_ID`, `GARAGE_S3_SECRET_ACCESS_KEY`, `GARAGE_S3_BUCKET`. When ALL four are set on the parent process at construction time, the fixture MUST enter env-bridge passthrough mode. #### Scenario: env() returns the full four-key set - **WHEN** the fixture has started - **THEN** `fixture.env()` returns a record with exactly the four declared keys ### Requirement: Seven-step initialisation sequence In container mode `start()` SHALL execute the following sequence against the admin port, in order, before declaring the fixture healthy: 1. `status` — read cluster status to discover the node id 2. `layout assign` — assign capacity to the discovered node 3. `layout apply` — commit the layout version 4. `bucket create` — create the configured bucket 5. `key create` — create an access/secret key pair 6. `bucket allow` — grant the key full permissions on the bucket 7. `key info` — read back the key to confirm and capture the secret Any step failure MUST abort the sequence and reject `start()` with a coded error naming the failing step. #### Scenario: Successful seven-step init - **WHEN** `start()` is invoked against a fresh container - **THEN** all seven admin operations execute in the order above before `start()` resolves #### Scenario: Layout-apply failure aborts - **WHEN** the third step (`layout apply`) fails - **THEN** `start()` rejects with `code === "INTEGRATION.GARAGE_S3.LAYOUT_APPLY_FAILED"` AND steps 4–7 are not attempted ### Requirement: Healthcheck contract — admin port `isHealthy()` SHALL probe the admin port (3902) with an HTTP request and resolve `true` only when the admin endpoint responds. The S3 port (3900) alone is NOT sufficient evidence of health. #### Scenario: Admin port is the source of truth - **WHEN** the S3 port is open but the admin port has not yet started - **THEN** `isHealthy()` resolves `false` ### Requirement: Configurable timeouts The fixture options SHALL accept `healthIntervalMs?: number` and `healthTimeoutMs?: number` overrides forwarded to `HealthPoller`. Defaults follow `HealthPoller`'s defaults (500 ms / 120 s). #### Scenario: Override applied - **WHEN** the fixture is constructed with `{ healthTimeoutMs: 30000 }` - **THEN** `HealthPoller` rejects after 30 s when the container never becomes healthy ### Requirement: Injectable factories The fixture constructor SHALL accept optional `containerFactory`, `execFn`, and `fetchFn` parameters for the container, admin-CLI exec, and admin-port HTTP calls respectively. Defaults invoke testcontainers, the container's exec API, and the global `fetch`. #### Scenario: Injected execFn drives the seven-step sequence - **WHEN** `execFn` is replaced with a stub that records every command - **THEN** the recorded commands match the seven-step order ### Requirement: Error-code prefix Errors thrown from the plugin SHALL carry codes matching `^INTEGRATION\.GARAGE_S3\.[A-Z_]+$`. At minimum the package SHALL define: `START_FAILED`, `STOP_FAILED`, `STATUS_FAILED`, `LAYOUT_ASSIGN_FAILED`, `LAYOUT_APPLY_FAILED`, `BUCKET_CREATE_FAILED`, `KEY_CREATE_FAILED`, `BUCKET_ALLOW_FAILED`, `KEY_INFO_FAILED`. #### Scenario: Each step has its own coded error - **WHEN** `bucket create` fails - **THEN** the rejection's `code === "INTEGRATION.GARAGE_S3.BUCKET_CREATE_FAILED"` AND `context.step === "bucket-create"`