Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions .devcontainer/DEVCONTAINER.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,29 @@ Note the devcontainer.json setup with
},
"mounts": [
"source=${env:SSH_AUTH_SOCK},target=/ssh-agent,type=bind",
"source=/mnt/c/Users/klogan/.ssh,target=/root/.ssh-host,type=bind,readonly"
"source=${localEnv:HOME}/.ssh,target=/root/.ssh-host,type=bind,readonly",
"source=${localEnv:HOME}/.microsoft/usersecrets,target=/root/.microsoft/usersecrets,type=bind"
]

### Secrets Management

- Local development: Use .NET User Secrets or environment variables
- CI/CD: Use GitHub repository secrets or Azure Key Vault
- DevContainer environment variables in `devcontainer.json` are for non-sensitive values only
- Persist User Secrets in Dev Containers by bind-mounting your host user-secrets directory to `/root/.microsoft/usersecrets`

Example mount paths by host OS:
- Linux/macOS host: `source=${localEnv:HOME}/.microsoft/usersecrets,target=/root/.microsoft/usersecrets,type=bind`
- Windows host: `source=${localEnv:APPDATA}/Microsoft/UserSecrets,target=/root/.microsoft/usersecrets,type=bind`

If you use Docker Compose directly, the same mapping applies:

```yaml
services:
api:
volumes:
- ${HOME}/.microsoft/usersecrets:/root/.microsoft/usersecrets
```

## Advanced Customization

Expand Down Expand Up @@ -180,7 +195,8 @@ Edit `mounts` array to add more host directories (e.g., for shared data):

```json
"mounts": [
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh,target=/root/.ssh,readonly",
"source=${localEnv:HOME}/.ssh,target=/root/.ssh-host,type=bind,readonly",
"source=${localEnv:HOME}/.microsoft/usersecrets,target=/root/.microsoft/usersecrets,type=bind",
"source=/path/on/host,target=/path/in/container"
]
```
Expand Down
5 changes: 4 additions & 1 deletion .devcontainer/devcontainer.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ RUN curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/s
&& apt-get update \
&& apt-get install -y --no-install-recommends podman nodejs \
&& rm -rf /var/lib/apt/lists/* \
&& npm --version
&& npm --version \
# Security hardening: delay installing very new publishes and block lifecycle scripts by default.
&& npm config set --global min-release-age 1440 \
&& npm config set --global ignore-scripts true

# Ensure the SDK version from global.json is available in the image.
RUN if dotnet --list-sdks | grep -q "^${REQUIRED_DOTNET_SDK_VERSION}"; then \
Expand Down
5 changes: 3 additions & 2 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "Bike Tracking - Full Stack",
"name": "Bike Commuter Tracker - Full Stack",
"build": {
"dockerfile": "devcontainer.Dockerfile",
"context": ".."
Expand Down Expand Up @@ -70,6 +70,7 @@
"PATH": "${containerEnv:PATH}:/usr/local/share/dotnet-tools:/root/.dotnet/tools"
},
"mounts": [
"source=/mnt/c/Users/klogan/.ssh,target=/root/.ssh-host,type=bind,readonly"
"source=${localEnv:HOME}/.ssh,target=/root/.ssh-host,type=bind,readonly",
"source=/mnt/c/Users/klogan/AppData/Roaming/Microsoft/UserSecrets,target=/root/.microsoft/usersecrets,type=bind"
]
}
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:

- name: Install frontend dependencies
working-directory: src/BikeTracking.Frontend
run: npm ci
run: npm ci --ignore-scripts

- name: Install Playwright browser and system dependencies
working-directory: src/BikeTracking.Frontend
Expand Down
8 changes: 7 additions & 1 deletion .specify/memory/constitution.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Modified Sections:
- Compliance Audit Checklist: Added modular boundary and contract compatibility checks
- Guardrails: Added non-negotiable interface/contract boundary rules for cross-module integration
Status: Approved — modular architecture and contract-first parallel delivery are now constitutional requirements
Current Update (v1.12.2): Added mandatory spec-completion gate requiring database migrations to be applied and E2E tests to pass before a spec can be marked done.
Current Update (v1.12.3): Added mandatory per-migration test coverage governance requiring each migration to include a new or updated automated test, enforced by a migration coverage policy test in CI.
Previous Update (v1.12.2): Added mandatory spec-completion gate requiring database migrations to be applied and E2E tests to pass before a spec can be marked done.
Previous Updates:
- v1.11.0: Strengthened TDD mandate with a strict gated red-green-refactor workflow requiring explicit user confirmation of failing tests before implementation.
- v1.10.2: Codified a mandatory post-change verification command matrix so every change runs explicit checks before merge.
Expand Down Expand Up @@ -291,6 +292,7 @@ A vertical slice is **production-ready** only when all items are verified:
- [ ] Post-change verification matrix executed for the impacted scope and evidence recorded
- [ ] Feature branch deployed locally via `dotnet run` (entire Aspire stack: frontend, API, database)
- [ ] Integration tests pass; manual E2E test via Playwright (if critical user journey)
- [ ] Every migration introduced by the slice includes a new or updated automated test and an updated migration coverage policy mapping entry
- [ ] All validation layers implemented: client-side (React validation), API (DTO DataAnnotations), database (constraints)
- [ ] Events stored in event table with correct schema; projections materialized and queryable
- [ ] Module boundaries preserved; cross-module interactions occur only via approved interfaces/contracts with compatibility evidence
Expand Down Expand Up @@ -362,6 +364,7 @@ Tests suggested by agent must receive explicit user approval before implementati

**Database Tests**
- Migration up/down transitions
- Migration coverage policy test must map every discovered migration to a new or updated automated test action
- Event table constraints (unique EventId, non-null fields)
- Foreign key integrity for aggregates
- DataAnnotations constraints validated at database layer
Expand Down Expand Up @@ -394,6 +397,7 @@ Tests suggested by agent must receive explicit user approval before implementati
14. **User Acceptance**: User validates slice meets specification and data validation rules observed
15. **Phase Completion Commit**: Before starting the next phase, create a dedicated phase-completion commit that includes completed tasks and verification evidence for that phase
16. **Spec Completion Gate**: Before marking any specification as done, database migrations for that spec must be applied successfully to the target local runtime database and the spec's end-to-end (Playwright) tests must run green
17. **Migration Test Coverage Gate**: Every migration added or modified in a branch must include a new or updated automated test and must be represented in the migration coverage policy test map before merge

### Compliance Audit Checklist

Expand All @@ -410,6 +414,7 @@ Tests suggested by agent must receive explicit user approval before implementati
- [ ] TDD gate commits created: red baseline commit, green commit, and separate refactor commit when applicable
- [ ] Phase completion commit created before moving to the next phase
- [ ] Database migrations for the spec are created and applied successfully to the runtime database used for validation
- [ ] Every migration introduced or modified by the spec has a corresponding new or updated automated test and a migration-coverage policy entry
- [ ] Spec-level E2E (Playwright) suite executed and passing before spec marked complete
- [ ] All SAMPLE_/DEMO_ data removed from code before merge
- [ ] Secrets NOT committed; `.gitignore` verified; pre-commit hook prevents credential leakage
Expand Down Expand Up @@ -445,6 +450,7 @@ Breaking these guarantees causes architectural decay and technical debt accrual:
- **TDD cycle is strictly gated and non-negotiable** — implementation code must never be written before failing tests exist, have been run, and the user has reviewed and confirmed the failures. The sequence is always: plan tests → write tests → run and prove failure → get user confirmation → implement → run after each change → verify all pass → consider refactoring. Skipping or reordering any step is prohibited.
- **Commit gates are mandatory for TDD and phase transitions** — every TDD gate transition requires a commit (red, green, and refactor when performed), and every completed phase requires a dedicated phase-completion commit before proceeding.
- **Spec completion requires migration + E2E gates** — a spec cannot be marked done until its database migrations are applied to the runtime database and its Playwright E2E scenarios pass.
- **Every migration requires a test update** — each migration must ship with a new or updated automated test and an updated migration coverage policy entry; changes are blocked when migration coverage is incomplete.
- **Expected-flow C# logic uses Result, not exceptions** — validation, not-found, conflict, and authorization business outcomes must be returned via typed Result objects (including error code/message metadata). Throwing exceptions for these expected outcomes is prohibited; exceptions are only for truly unexpected failures.
- **Cross-module work is contract-first and interface-bound** — teams must integrate through explicit interfaces and versioned contracts only; direct coupling to another module's internal implementation is prohibited.
- **No Entity Framework DbContext in domain layer** — domain must remain infrastructure-agnostic. If domain needs persistence logic, use repository pattern abstracting EF.
Expand Down
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"/^cd /workspaces/neCodeBikeTracking && pwsh \\.specify/scripts/powershell/check-prerequisites\\.ps1 -Json$/": {
"approve": true,
"matchCommandLine": true
}
},
"mkdir": true
},
"sqltools.connections": [
{
Expand Down
5 changes: 5 additions & 0 deletions aspire.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"appHost": {
"path": "src/BikeTracking.AppHost/BikeTracking.AppHost.csproj"
}
}
35 changes: 35 additions & 0 deletions specs/010-gas-price-lookup/checklists/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Specification Quality Checklist: Gas Price Lookup at Ride Entry

**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-03-31
**Feature**: [spec.md](../spec.md)

## Content Quality

- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed

## Requirement Completeness

- [x] No [NEEDS CLARIFICATION] markers remain — FR-012 resolved: EIA API with team-managed key; secret storage deferred to a separate concern
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified

## Feature Readiness

- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification

## Notes

- FR-010 resolved: EIA API (U.S. government source) with a free team-managed API key. Secret storage mechanism (e.g., KeyVault, environment variable) is deferred and out of scope for this feature.
- All items pass. The spec is ready for `/speckit.plan`.
168 changes: 168 additions & 0 deletions specs/010-gas-price-lookup/contracts/api-contracts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# API Contract: Gas Price Lookup Endpoint

**Feature**: 010-gas-price-lookup
**Owner**: BikeTracking.Api
**Consumer**: BikeTracking.Frontend

---

## GET /api/rides/gas-price

Retrieves the national average retail gasoline price for a given date, using a local durable cache backed by the EIA API.

### Request

```
GET /api/rides/gas-price?date=YYYY-MM-DD
Authorization: Bearer {token}
```

**Query Parameters**

| Parameter | Type | Required | Constraints | Notes |
|---|---|---|---|---|
| `date` | string (YYYY-MM-DD) | Yes | Valid ISO date format | The ride date to look up the gas price for. |

### Response: 200 OK

```json
{
"date": "2026-03-31",
"pricePerGallon": 3.1860,
"isAvailable": true,
"dataSource": "EIA_EPM0_NUS_Weekly"
}
```

**When unavailable** (API down, no data, future date with no coverage):
```json
{
"date": "2100-01-01",
"pricePerGallon": null,
"isAvailable": false,
"dataSource": null
}
```

### Response: 400 Bad Request

Returned when `date` is missing or not a valid date string.

```json
{
"error": "invalid_request",
"message": "date query parameter is required and must be a valid date in YYYY-MM-DD format."
}
```

### Response: 401 Unauthorized

Returned when no valid bearer token is present.

### Notes

- This endpoint never returns a 5xx for EIA lookup failures. EIA failures are absorbed and reflected as `isAvailable: false` with `pricePerGallon: null`.
- The response is deterministic for any given date: once a price is cached, the same value is always returned for that date.
- The `date` parameter is used as the cache key. The actual EIA period date (the Monday of the survey week) may differ; it is not exposed in this contract.

---

## Modified Contract: GET /api/rides/defaults

Extends the existing defaults endpoint to include the most recent ride's gas price.

### Response: 200 OK (extended)

Adds `defaultGasPricePerGallon` to the existing response:

```json
{
"hasPreviousRide": true,
"defaultRideDateTimeLocal": "2026-03-31T07:30:00",
"defaultMiles": 5.2,
"defaultRideMinutes": 22,
"defaultTemperature": 58.0,
"defaultGasPricePerGallon": 3.1860
}
```

When no previous ride exists, or the most recent ride has no gas price:
```json
{
"hasPreviousRide": false,
"defaultRideDateTimeLocal": "2026-03-31T08:00:00",
"defaultGasPricePerGallon": null
}
```

**Backwards compatibility**: `defaultGasPricePerGallon` is a new nullable field. Existing clients that ignore it continue to work.

---

## Modified Contract: POST /api/rides (Record Ride)

Adds `gasPricePerGallon` to the existing request body.

### Request (extended)

```json
{
"rideDateTimeLocal": "2026-03-31T07:30:00",
"miles": 5.2,
"rideMinutes": 22,
"temperature": 58.0,
"gasPricePerGallon": 3.1860
}
```

| Field | Type | Required | Constraints |
|---|---|---|---|
| `gasPricePerGallon` | number | No | Must be > 0 and ≤ 999.9999 when provided. Null/omitted means unavailable. |

**Backwards compatibility**: Existing requests that omit `gasPricePerGallon` continue to work; the field defaults to null.

---

## Modified Contract: PUT /api/rides/{rideId} (Edit Ride)

Adds `gasPricePerGallon` to the existing request body.

### Request (extended)

```json
{
"rideDateTimeLocal": "2026-03-31T07:30:00",
"miles": 5.2,
"rideMinutes": 22,
"temperature": 58.0,
"gasPricePerGallon": 3.1860,
"expectedVersion": 2
}
```

| Field | Type | Required | Constraints |
|---|---|---|---|
| `gasPricePerGallon` | number | No | Must be > 0 and ≤ 999.9999 when provided. Null/omitted means price not available. |

**Backwards compatibility**: Existing clients that omit `gasPricePerGallon` continue to work.

---

## Modified Contract: GET /api/rides/history (Ride History Row)

Adds `gasPricePerGallon` to each ride row in the history response.

### RideHistoryRow (extended)

```json
{
"rideId": 42,
"rideDateTimeLocal": "2026-03-31T07:30:00",
"miles": 5.2,
"rideMinutes": 22,
"temperature": 58.0,
"gasPricePerGallon": 3.1860
}
```

**Backwards compatibility**: New nullable field; existing consumers that ignore it continue to work.
Loading
Loading