# Generalization Contract

The rules for turning a client-specific change into a template-safe pattern. Applied by
`generalize-pattern`, and the eligibility half is applied by `detect-upstream-candidates`.

The harvest loop reads client code into a Gemma-controlled process and writes generalized
patterns into shared template repos. This contract is the load-bearing safety control,
because eligibility is broad (everything *except* the denylist) and the output is a direct
code PR. Two failure modes it prevents: (1) leaking client specifics/secrets into a shared
template, and (2) polluting the template with non-portable, client-only logic.

## Denylist — never read, never harvest

Eligibility is decided by **path**. Never open these, even to inspect:

```
**/.env*                  environment files of any stage
**/*secret*               anything with "secret" in the name
**/*credential*           credential files
**/*.pem  **/*.key        private keys
**/*.p12  **/*.pfx         keystores
**/data/**                data files / exports
**/seeds/**               dbt seeds (may contain real rows)
**/dags/**                DAG business logic (harvest infra/config, not pipelines)
client-named modules, fixtures, and files containing client/company identifiers
```

If a pattern lives only inside a denylisted path, it is **not eligible** — drop it. Do not
copy a denylisted file elsewhere to read it; do not decode, transform, or pipe it. This
mirrors the Gemma secrets-protection policy: there is no safe way to read credentials.

## Always strip — replace with template placeholders

Even in eligible files, the following client specifics must be removed and replaced with a
`<placeholder>` token or the template's existing convention:

| Category | Examples | Replace with |
|---|---|---|
| Identity | client/company/project/repo names, team handles | `<project_name>`, template default |
| Network | hostnames, IPs, URLs, VPN endpoints | `<host>`, `<service_url>` |
| Data locators | bucket / dataset / schema / database / table names | `<schema>`, `<bucket>` |
| Config values | client-specific ports, paths, schedules, regions | template default or `<placeholder>` |
| Sources | specific source systems / connection targets | generalized example or placeholder |
| Business rules | client-specific thresholds, mappings, filters | excluded — not harvested |

Secrets, tokens, keys, and passwords are **never** included in any form. Reference them via
env vars / secret manager exactly as the template already does.

## Harvest the pattern, not the payload

Harvest the *generalizable shape* — a hardening setting, a CI step, a Dockerfile idiom, a
dev-experience tool, a docs structure. Do **not** harvest the client's content that flows
through that shape (their DAGs, their models, their data, their specific integrations).

If the pattern cannot be expressed without a client specific, drop that part. If the whole
candidate cannot be generalized cleanly, abort the candidate and record why — never propose
a half-generalized change.

## Fit the template, not the client

The generalized change must match the **template's** conventions: its formatting, naming,
comment density, file layout, and existing placeholder style. The result should read as if
the template's author wrote it. A pattern copied with the client's stylistic fingerprint is
not done being generalized.

## Verify before proposing

`generalize-pattern` self-checks its own diff (its Step 4) — this is the first line of
defence and must catch leaks on its own. The orchestrating workflow is **required** to
repeat an equivalent mechanical check as a gate before any PR is opened; that requirement is
specified in `required-ci-gates.md` (the workflow itself lives in `Gemma-Analytics/.github`,
not in this plugin). Both layers check:

- The diff contains no client identifier from the candidate's `sources[]`.
- The diff contains no secret-shaped strings.
- No denylisted file was touched.
- The change is self-contained and portable.

A failure means fix the generalization or drop the candidate — never ship a leak. Do not
treat CI as the only safeguard: the skill must not emit a leak in the first place.

## Why corroboration does not relax this

Cross-client corroboration (2+ clients independently doing the same thing) lowers the bar
for *whether a candidate is proposed* — it does **not** lower the stripping standard. A
corroborated pattern is generalized exactly as strictly as a single-source one. Convergence
is evidence of general value, not a license to carry client specifics upstream.
