# GCS-TEST-001 - Test and CI Enforcement Contract

Spec ID      : GCS-TEST-001
Title        : Test and CI Enforcement
Version      : v1.0
Status       : Settled
Owner        : Phil
Approver     : Phil
Settled      : 2026-07-08
Depends on   : GCS-SYS-001, GCS-NAME-001, GCS-STACK-001, GCS-RUNTIME-001, GCS-DEPLOY-002
Supersedes   : -

## 1. Principle

A contract clause that cannot be tested is not yet a real constraint. Every
implementation file declares its boundary promise in a contract header. Tests
and validators assert input -> output behavior at that boundary, not private
implementation details.

GCS tests may fake external public data services at the boundary. They may not
mock GCS application modules to make other GCS modules pass.

## 2. Canonical Gate

```bash
python3 -m tools.check_contracts --check
python3 -m tools.check_contracts --self-test
```

The CI workflow `.github/workflows/contract-check.yml` runs both commands.

## 3. Current Gate Set

| Gate | Asserts | Failure mode |
| --- | --- | --- |
| Register coverage | Every Settled contract artifact exists and every `contracts/GCS-*.md` file is registered | Missing file or unregistered contract |
| Settled metadata | Contract artifacts carry `Status : Settled` or `Status:** Settled` | Draft/unsettled implementation dependency |
| Source headers | `.html`, `.js`, `.py`, `.yml`, `.yaml` source files carry the required contract header labels | Missing measurable input/output promise |
| Env var docs | Every `GCS_*` variable found in source/config is documented in `ops/local_variables.example` | Undocumented runtime input |
| No async queue | Source files contain no queue/broker implementation tokens while GCS-RUNTIME-001 is active | Queue implementation without contract |
| Container drift | Active/config files contain no `container_name`, forbidden broker service, or uncontracted host port | Runtime collision risk |
| App/test import | Future `app/` code may not import `tests/` | Test-only code leakage |
| Phase 0B allowlist | Only `app/__init__.py`, `app/raw_store/`, `app/normalization/`, `app/sources/fixtures/`, `tests/raw_store/`, `tests/normalization/`, and `tests/fixtures/` may appear as implementation paths | Uncontracted runtime surface |
| API/persistence absence | `api/`, `app/api/`, `migrations/`, DB models, SQL files, and database URL usage in app code cannot appear before inventory amendments | Uncontracted runtime surface |
| Live source-client absence | Phase 0B contains no live HTTP clients; fixtures only | Live public data pull before storage gates |
| Phase 0C persistence inventory | `GCS-DATA-003` names required physical tables, columns, enums, deterministic constraints, indexes, migration order, rollback policy, point-in-time query rules, and Phase 0D proof tests | Phase 0D would need to invent persistence shape locally |
| Phase 0D implementation allowance | Only `app/persistence/`, `migrations/NNNN_<snake_case>.py`, `tests/persistence/`, and persistence validation tooling may be added beyond Phase 0B paths | Persistence implementation leaks into API, sources, queues, containers, analytics, or prediction |
| Phase 0D database-token confinement | Database implementation tokens may appear only in `app/persistence/`, `migrations/`, `tests/persistence/`, or `tools/check_persistence_schema.py` | DB access appears in raw store, normalization, source clients, API, or unrelated tools |

## 4. Positive Verification

For the current Phase 0 repository:

```bash
python3 -m tools.check_contracts --check
```

Expected output includes `GCS_CONTRACT_CHECK_OK`.

## 5. Negative Verification

These changes must fail the gate:

- Remove `Outputs guaranteed:` from a source header.
- Add `GCS_DATABASE_URL` to code without documenting it.
- Add `queue_name = "gcs.ingest"` to source code.
- Add a Compose service named `gcs-redis`.
- Add `container_name:` to a Compose file.
- Add `api/routes.py` without amending GCS-API-001.
- Add `app/signals/`, `app/entity_resolution/`, `migrations/`, live HTTP
  clients, database URL usage in app code, or any queue/worker/scheduler token
  during Phase 0B.
- Remove a required Phase 0C persistence table, enum, deterministic constraint
  naming rule, migration-order rule, rollback rule, point-in-time query rule,
  or Phase 0D proof-test requirement from `GCS-DATA-003`.
- Add database implementation tokens outside `app/persistence/`, `migrations/`,
  `tests/persistence/`, or `tools/check_persistence_schema.py`.
- Add a migration file not named `migrations/NNNN_<snake_case>.py`.
- Add a standalone `.sql` file.
- Add API routes, active Compose files, database containers, live HTTP source
  clients, queue/worker/scheduler tokens, entity-resolution code, signal code,
  analytics code, or prediction code during Phase 0D.

## 6. Future Phase 0D Persistence Test Contract

Before Phase 0D may exit, future persistence tests must assert input -> output
behavior at the persistence boundary. The required tests are:

| Test file | Positive behavior | Negative behavior |
| --- | --- | --- |
| `tests/persistence/test_phase_0d_schema_inventory.py` | Contracted tables, columns, enums, constraints, and indexes exist exactly as named | Missing table, anonymous constraint, unnamed index, or ad-hoc text enum fails |
| `tests/persistence/test_phase_0d_raw_normalized_round_trip.py` | Phase 0B fixture persists as one raw payload row and one deterministic source record | Missing `as_of_timestamp`, `fetch_timestamp`, `source_authority_tier`, or `raw_payload_digest` fails |
| `tests/persistence/test_phase_0d_point_in_time_queries.py` | Query at `query_as_of_timestamp` reads only knowable-as-of rows | Later `as_of_timestamp` or later `fetch_timestamp` row is rejected as lookahead leakage |
| `tests/persistence/test_phase_0d_idempotency.py` | Replaying the same fixture creates no duplicate logical rows | Duplicate natural key or idempotency key fails |

These future tests may use a contract-approved database boundary only after
GCS-DATA-003 and GCS-DEPLOY-002 permit that boundary. They may not mock GCS
persistence code to pass.

## 7. Phase 0D Eligibility Gate

After the approved Phase 0D frame, the checker may open only these additional
paths:

- `app/persistence/`
- `migrations/NNNN_<snake_case>.py`
- `tests/persistence/`
- `tools/check_persistence_schema.py`

The checker must still fail on:

- `api/`
- `app/api/`
- `app/entity_resolution/`
- `app/signals/`
- `app/prediction/`
- active Compose files
- database containers
- standalone `.sql` files
- live HTTP source clients
- queues, workers, schedulers, Redis, RabbitMQ, or Celery
- database implementation tokens outside Phase 0D persistence paths

Positive validation command:

```bash
python3 -m tools.check_contracts --self-test
```

Negative validation: the self-test must synthesize allowed Phase 0D
persistence paths and forbidden runtime surfaces, and it must fail if the
checker accepts a forbidden surface.

## 8. Acceptance

The repository is contract-conformant only when local and CI checks pass from a
clean checkout.
