Files
club-3090/docs/PATCH_POLICY.md
noonghunna 999c93fe8c feat(pull): v0.8.2 STEP V3 — arch-registry expansion + chat-template attribution/drift_guard
CONTRACT-2 (§10-R4) arch-family registry expansion: +13 safetensors arch
rows in arch_patches.yml (PhiForCausalLM — the microsoft/phi-2 STEP V1
on-rig no-arch-row anchor — Phi3Small, Gemma/Gemma3/Gemma3-CG, Starcoder2,
Cohere, InternLM2, Mixtral/Qwen2Moe/Qwen3Moe MoE, Qwen2-VL). Additive data
only, zero [C0]/decision-logic change. Zero false-pass by construction:
each follows the established estimated-lower-bound/unverified-TRC precedent
so [C0] still resolves needs-trust-remote-code-ack (fail-closed, bypassable
ONLY by --trust-remote-code) — the expansion drops only the
--experimental-arch requirement, never auto-passes; an arch still absent
still hard-blocks no-arch-row. test-pullgate-gates.sh proves both, plus the
#146-shape worked acceptance case (a hand-added awq_bf16_int4 weights
variant the expanded flag schema/parity machinery absorbs cleanly).

CONTRACT-2b-i chat-template attribution + behavioral drift_guard: new
`chat_template` delivery class (VALID_DELIVERY_MECHANISM); froggeric (22
composes — 18 direct + 4 nvlink* via REAL Docker Compose extends: merge)
and carnice (mount-only) brought under load_bearing_when + a behavioral
drift_guard whose check encodes the self-contained symmetric restart+settle
protocol (identical docker restart both arms, /v1/models healthy, 60s
settle, >=3 bench runs/arm, grand-mean same-segment compare, flag only a
3/3 deterministic regression). Effective coverage uses REAL merge
semantics: docker compose config (preferred) or a deterministic offline
extends: merge applying the same rules (additive sequence merge; `!reset`
removal) — never the unsound single-base text concat. .jinja artifact
discovery catches an orphan vendored template. test-patch-attribution.sh
adds the class checks + an H4 fixture asserting a `!reset` child AND a
stopped-extending child both lose coverage (the false-negative is the
dangerous direction). Generator emit kept in lock-step with reaches().
Documented as PATCH_POLICY.md §3.1. Rig-independent leak assertions added
(str(abs_dir) not in shared; repo-relative-only — never a /opt|/home
substring allowlist).

RED-LINE: gates.py/pull.py/deriver.py/capture.py/loop_input.py/
classifier.py/dedup.py/submit_pull.py/kv-calc.py/failure_fingerprints.yml
byte-unchanged; no shipped compose changed; patch_attribution.py c0_state/
is_artifact/compose_text/service_body byte-identical (additive only). Full
test-*.sh suite green in the CI condition; kv-calc --calibration N/N.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 18:29:58 +00:00

274 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Patch Policy — the contributor contract for load-bearing patches (v0.8.0, #141)
This is the **durable contract** for anyone (human or agent) adding,
modifying, or removing a patch that the v0.8.0 compose generator
([`scripts/generate-compose.sh`](../scripts/generate-compose.sh)) consumes.
It is prescriptive and self-contained: follow it exactly, and
`scripts/tests/test-patch-attribution.sh` + `scripts/tests/test-generate-compose.sh`
will gate your PR cleanly.
It exists for one reason, stated up front so it frames every rule below:
> ### The #145 lesson
> A patch can be present in the repo, declared "load-bearing", and *still
> never reach the running engine* — silently. That is the
> `#72` / `#145`-class failure: a load-bearing fix that the compose does not
> actually deliver, with no error, just a quietly-wrong server. **Every
> mechanism in this document exists to make a silently-undelivered
> load-bearing patch impossible to introduce without it failing loud.**
For the data model these rules sit on, see
[`docs/PATCH_ATTRIBUTION.md`](PATCH_ATTRIBUTION.md). For how the generator
*uses* this metadata, see [`docs/COMPOSE_GENERATOR.md`](COMPOSE_GENERATOR.md).
---
## 1. Where patches and metadata live
| File | Role |
|---|---|
| `models/<model>/<engine>/patches/<patch-dir>/` | the patch artifact itself (overlay file(s), sidecar `.py`, or `install.sh`) |
| `scripts/lib/profiles/patches.yml` | the patch entry — what it fixes, how it is delivered, where it is load-bearing, its drift-guard |
| `scripts/lib/profiles/arch_patches.yml` | declarative arch → engine-pin / valid-TP / trc gate (C0 authority; **strict closed key-set** — do not add keys) |
| `scripts/lib/profiles/profile_runtime.yml` | per-profile captured template + `genesis_equipped`; `arch_model_xref` trc evidence ledger |
**Add the `patches.yml` entry in the same change as the patch artifact.**
Never land a patch file without its entry, and never land an entry that
points at a missing artifact.
---
## 2. Required per-patch metadata
A load-bearing patch entry in `patches.yml` MUST carry all of:
| Field | Requirement |
|---|---|
| `id` | unique, stable, descriptive |
| `model` | list of model slugs the patch applies to |
| `files` | repo-relative path(s) to the patch artifact |
| `load_bearing_when[].composes` | the **exact** list of profile keys this patch is load-bearing for. This is the *only* thing the generator keys patch selection off (`X ∈ P.load_bearing_when[].composes`). Each entry also carries `reason` + `evidence`. |
| `delivery_mechanism` | one of `python_sidecar`, `site_package_overlay`, `install_script`, `chat_template` (vendored `.jinja` override — see §3.1), or `none` (diagnostics / negative-result / Genesis-env patches) |
| `delivery_spec` | the concrete wiring: mount target(s) (`overlay_files[].dest`, `overlay_dir`+`dest_root`, `mounted_at`), sidecar/script path, `invoke` command, and `wired_at` (`volumes`, `entrypoint`, or both). This is the **single source** both the generator's emit and `reaches()`'s reachability validation read — they must agree by construction. |
| `drift_guard` | **mandatory** for any load-bearing patch (`kind`, `check`, `on_fail`). See §4. |
| `capability` | the capability the patch delivers (e.g. `tp-weight-load`, `tool-choice-required`, `tool-call-stream`, `dflash-spec-decode`) |
| `foundational` | `true` if weights/boot cannot proceed without it; `false` if absence merely loses a capability. Drives the §4.1 grade. |
| `upstream` | `ref` + `status` + `drop_when` — the condition under which the patch should be removed |
| `status` | `verified`, `unverified`, or `suspect`. Use `unverified` honestly — never claim `verified` without evidence. The `load_bearing_when[].evidence` string must point at a real doc/README anchor. |
| `delivery_gaps[]` | declare here, do **not** silently fix, any profile where the patch is load-bearing but cannot reach the compose today (the #145-class boundary). Each gap carries `composes` + an `issue` string. |
The legacy `delivery:` boolean block
(`dockerfile_bake`/`entrypoint_invoke`/`genesis`) is **DEPRECATED and
READ-ONLY** — retained verbatim only because `test-patch-attribution.sh`
still reads it. Do **not** put new wiring decisions there; use
`delivery_mechanism` / `delivery_spec`.
Diagnostics / no-op / negative-local-result / Genesis-env patches set
`delivery_mechanism: none` and have no `delivery_spec`.
---
## 3. Hard rule: guarded `python_sidecar` is the default delivery mechanism
Choose the delivery mechanism in this order of preference:
1. **`python_sidecar` (DEFAULT).** A guarded sidecar `.py` invoked from the
entrypoint before `vllm` imports/serves. This is the locked default for
any new patch unless there is a concrete reason it cannot work.
2. **`install_script`.** A mounted `install.sh` invoked from the entrypoint
(`invoked_before: vllm-import`). Use when the change is a multi-step
install rather than a single Python shim.
3. **`site_package_overlay` (LAST RESORT).** A bind-mount over a
`dist-packages/...` file. Permitted **only** for an isolated, single-file
emergency. It carries the **#145-class risk** (a stale/conflicting overlay
silently shadows upstream — overlays of `speculative.py` /
`gpu_model_runner.py` etc. conflict with each other; only one variant can
run at a time) and **must be re-homed** to a sidecar or upstream fix as
soon as the emergency is over. A multi-file overlay must record its
`file_count` and a `note` flagging the #145-risk and any conflicting
overlay it is mutually exclusive with.
If you reach for `site_package_overlay`, justify it in the PR and file the
re-home follow-up.
### 3.1 The `chat_template` delivery class
A **model chat-template override** (a vendored `.jinja` mounted into the
container) is its own delivery class — `delivery_mechanism: chat_template`.
A bad/regressed/re-vendored template is exactly the **#145 silent-break
class** (tool-call XML / reasoning delimiters / streaming), so it MUST be
attributed and drift-guarded like any other load-bearing patch, not left
invisible to the generator and `test-patch-attribution`.
`delivery_spec` for a `chat_template` patch carries:
- `jinja` — repo-relative path to the vendored `.jinja` (it is also the
patch's `files[]` entry; an orphan `.jinja` under a model `patches/`
tree with no `chat_template` patch is a hard test failure);
- `mounted_at` — the container path the `.jinja` is bind-mounted to;
- `mount_mode` — typically `ro`;
- `invoke` — the serving wiring. Two in-tree styles:
- **explicit** `--chat-template <mounted_at>` (e.g. froggeric):
`wired_at: [volumes, entrypoint]`;
- **mount-only** (e.g. carnice mounts over the model dir's
`chat_template.jinja` and vLLM auto-loads it): `wired_at: [volumes]`,
no `--chat-template` arg;
- `wired_at` — as above.
**Effective coverage is computed from the REAL merged compose graph.**
A `chat_template` patch's reachability is resolved with **Docker Compose
`extends:` merge semantics** (`docker compose -f <child> config`, or a
deterministic offline merge that applies the same rules), **never a raw
single-base text concat**. This is mandatory because a child compose can
`!reset` the base's mount/command or simply stop extending a
template-bearing base — a text concat would still "see" the base's mount
line and report coverage that no longer exists (a **false negative**
the dangerous direction; the #377 silent-drift mode). Note Compose merges
`extends:` *sequences* additively: a plain re-declared `volumes: []` does
**not** drop a base mount; only the `!reset` tag (or not extending the
base at all) removes it. `test-patch-attribution.sh` carries a fixture
asserting a `!reset` child and a stopped-extending child both lose
coverage.
The `drift_guard` for a `chat_template` patch is `kind: behavioral` and
its `check` MUST encode the **self-contained symmetric restart+settle
protocol**: identical `docker restart <container>` on BOTH arms → wait
for `/v1/models` healthy → fixed 60 s settle → ≥3 `bench.sh` runs/arm →
compare the **grand mean** of the SAME canonical bench segment (NARRATIVE
800-word essay + CODE; never mix segments, never a single run); flag
ONLY a deterministic regression reproduced across all 3 runs. A
non-symmetric guard fabricates phantom regressions (the asymmetric-restart
"7%" artifact) and gets ignored. The same guard must clear the *next*
template re-vendor.
---
## 4. Hard rule: no `drift_guard` ⇒ not load-bearing in a generated compose
A patch with **no `drift_guard`** is, by definition, **not load-bearing in a
generated compose**. The generator only wires a patch whose state is
"applies cleanly" — and "applies cleanly" *means* drift-guard-tested (a
locked decision). If you believe a patch is load-bearing, you MUST give it a
`drift_guard`; if you cannot articulate a guard, the patch is not eligible to
be wired by the generator.
`drift_guard` fields:
- `kind``import-and-boot` or `behavioral`.
- `check` — a concrete, testable statement of what "still applies" means on
the selected nightly.
- `on_fail` — the §4.1 grade (see below).
### 4.1 Drift fails loud — never silently repaired
The drift-guard is a runtime probe. The generator **cannot** run it at
generation time (no engine container), so it wires the maintainer-tested
patch and surfaces the guard for the boot leg / operator to re-run. When a
guard **fails**, the outcome is graded — and the generator **never repairs**:
| `on_fail` / `foundational` | Outcome | Rationale |
|---|---|---|
| `capability-degraded` (foundational: false) | patch **OMITTED**, compose flagged **DEGRADED**; run needs `--accept-degraded` (else exit 3). Patch is **never wired**. | The model still serves, minus one capability. Honest degradation beats a silently-broken capability. |
| `hard-refuse` / `foundational: true` | **hard-refuse** (exit 2), even *with* `--accept-degraded`. | Weights/boot depend on it; there is nothing safe to emit. |
**Gap-before-guard ordering:** a declared `delivery_gaps[]` entry covering
the profile is evaluated *before* the drift-guard — the patch is
selected-but-undelivered (wiring omitted, header WARNING) and the guard is
skipped. This is the structural acknowledgement of a known coverage
boundary; it is loud (header category [2]) by construction, never silent.
The contract: **drift never gets repaired, only flagged.** A failed
capability-scoped guard degrades; a failed foundational guard refuses;
neither edits the patch.
---
## 5. Hard rule: Genesis patches are arch-data only, never generator-emitted
The compose generator is **non-Genesis, vLLM-only** (a permanently locked
v0.8.x scope decision). Genesis patches:
- live in `patches.yml`/`arch_patches.yml` as **data only** (so the
attribution audit stays complete);
- set `delivery_mechanism: none` (they are env-gated, not generator-wired);
- are **never emitted** by the generator — any profile that is
`genesis_equipped: true` (compose has a `GENESIS_*` / `_genesis` token, or
`kv_format` starts `turboquant`) is a clean refusal.
Do not add a Genesis patch expecting the generator to wire it. It will not,
and that is intentional.
---
## 6. Contributor workflow
1. **Land the artifact + entry together.** Add the patch under
`models/<model>/<engine>/patches/<patch-dir>/` and its `patches.yml`
entry (all §2 fields) in the same commit.
2. **Pick the delivery mechanism per §3** (default `python_sidecar`).
Write the `delivery_spec` so `wired_at` and the mount/invoke markers
describe the *actual* wiring in the compose(s) you list under
`load_bearing_when[].composes`.
3. **Write a real `drift_guard` (§4).** No guard ⇒ the generator will not
wire it (§4 hard rule) — that is a design signal, not a workaround.
4. **Wire it into the shipped compose(s)** you declared, OR — if you cannot
reach a listed compose today — declare a `delivery_gaps[]` entry with an
honest `issue` string. **Do not silently fix runtime files instead of
declaring the gap** (that *is* the #145 failure mode).
5. **If the patch gates an arch/engine path**, extend the matching
`arch_patches.yml` row (`required_patches`, `engine_pin`, `valid_tp`,
`requires_trust_remote_code`). Do **not** add new top-level keys —
`arch_patches.yml` is a strict closed key-set; arch-scoped fold-ins go in
`profile_runtime.yml`'s `arch_model_xref`.
6. **Run the gates** (next section). Both must be green; the
patch-attribution summary must stay exactly
`61 patch / 11 arch / 18 calibration` *plus your delta* (the audit
re-counts — keep the known-gaps list intentional).
---
## 7. How the tests gate a patch PR
Two tests are the contract. They are the contract; patches are fixed to them,
never the reverse.
### `scripts/tests/test-patch-attribution.sh`
Imports `scripts/lib/profiles/patch_attribution.py` and audits every
`patches.yml` / `arch_patches.yml` / `calibration_seed.yml` entry. It checks:
schema (all §2 required keys present, valid `status`/`confidence`/trc
tri-state), the closed arch key-set, artifact coverage, and **reachability**:
> `reaches(root, patch, name_or_path)` parses the **comment-stripped service
> body only** (everything from the top-level `services:` key onward, header
> banner and `#` comments removed) and validates the patch's **actual
> `delivery_spec` wiring** — the declared mount target(s) and/or entrypoint
> invoke at the `wired_at` insertion point(s) — *not* a substring of the
> patch ID.
Consequences for a contributor:
- A patch ID merely *named in a comment/header* does **not** count as
reached — you must wire the real `delivery_spec` artifact.
- A patch declared load-bearing for a compose it does not actually reach
fails the audit **unless** you declared the `delivery_gaps[]` entry. The
audit prints the gap list explicitly — a silently-undelivered load-bearing
patch cannot pass.
### `scripts/tests/test-generate-compose.sh`
Generates the five golden triples and asserts the golden-parity invariant
(semantic diff confined to the two insertion points; constants + image
expression verbatim), that wired patches `reaches()` the *generated* compose,
that selected-but-undelivered patches are **not** reachable, that no
`--trust-remote-code` is emitted, and the full refusal/degraded matrix.
If your patch is load-bearing for a golden triple, it must wire cleanly into
the generated compose (or be a correctly-declared gap). If it changes the
captured service body of a golden profile, update the shipped compose so the
parity invariant holds — the generator captures, it never synthesizes.
**Both tests must be RC=0 before a patch PR merges.** A silently-undelivered
load-bearing patch is precisely the failure these gates exist to prevent — if
either test goes red, the contract has caught exactly what it is for.