Skip to content

feat: Procrustes feature-alignment / drift checker (framework 11 / 13.3) - #85

Open
mdheller wants to merge 1 commit into
mainfrom
feat/feature-alignment-drift-checker
Open

feat: Procrustes feature-alignment / drift checker (framework 11 / 13.3)#85
mdheller wants to merge 1 commit into
mainfrom
feat/feature-alignment-drift-checker

Conversation

@mdheller

@mdheller mdheller commented Aug 3, 2026

Copy link
Copy Markdown
Member

Closes #83. Refs #82 (cognition-decision-trace, merged), #76.

The gap

cognition-decision-trace.v1 (PR #82) declares the alignment_drift_exceeded approval trigger, but nothing computed the drift. This adds the checker behind it — the §13.3 feature-geometry alignment/drift gate that complements the §13.2 decision trace.

What ships

  • scripts/check-alignment.py — solves the orthogonal Procrustes alignment Q* = argmin ||F_A − F_B Q||_F as the orthogonal polar factor of M = F_B^T F_A (equivalently U Vᵀ of M = U Σ Vᵀ), then reports the aligned Frobenius residual as the drift metric. Both matrices are normalized to unit Frobenius norm first (scale-free residual). Drift is judged after alignment, so a rotated-but-equivalent space registers as aligned. Linear algebra is pure stdlib (Jacobi eigendecomposition of the small symmetric Gram matrix) — no third-party dependency added (there is no numpy/scipy in this repo).
  • schemas/lawful-learning/feature-alignment-drift.v1.json — the emitted record. This is a new, dedicated schema: the existing alignment-check.v1 is a governance-invariant structural check with no room for Procrustes/residual data. Schema teeth bind decision to classification (only stablealigned) and require a SHA-256 (FIPS 180-4) replay_seal.
  • Classification per framework §13.3: stable if residual < 0.05 and no critical feature drifted; drifted if residual < 0.20 or ≤1 critical feature drifted; broken otherwise. Checker exits non-zero once classification exceeds --max-class (default drifted), blocking promotion on broken.
  • Framework doc §12c pointer; wired into make feature-alignment-ci (folded into make validate and make lawful-learning-ci) plus schema teeth in make lawful-learning-schema-ci.

Teeth (verified locally, all green)

Case Result
near-identical space stable / aligned (residual 0.0052)
rotated-but-equivalent (40° rotation) aligned after Procrustes (residual 0.0) — proves alignment works, not raw distance
genuinely drifted space broken / drift, promotion blocked (residual 0.274, 3 critical drifted)
malformed: shape mismatch rejected (exit 2)
malformed: non-finite rejected (exit 2)
schema: decision/classification mismatch rejected
schema: weak replay seal rejected

make feature-alignment-ci, make lawful-learning-schema-ci, make lawful-learning-ci all exit 0.

Deferred (follow-up @mdheller)

Runtime calibration of the 0.05 / 0.20 thresholds against live gemma-2-9b-it SAE activations, and wiring the emitted record into the cognition loop's approval gate. Fixtures here are synthetic.

Closes the gap where cognition-decision-trace.v1 (PR #82) declares the
alignment_drift_exceeded approval trigger but nothing computes the drift.

Adds scripts/check-alignment.py: given two feature spaces over a shared
trunk output basis (e.g. a new model's SAE dictionary F_A vs the previous
model's F_B), it solves the orthogonal Procrustes alignment
Q* = argmin ||F_A - F_B Q||_F via the orthogonal polar factor of
M = F_B^T F_A (equivalently U V^T of M = U S V^T), then reports the aligned
Frobenius residual as the drift metric. Both matrices are normalized to unit
Frobenius norm first so the residual is scale-free. Because drift is judged
after alignment, a rotated-but-equivalent space registers as aligned. The
linear algebra is pure stdlib (Jacobi eigendecomposition of the small
symmetric Gram matrix) - no third-party dependency added.

Emits a feature-alignment-drift.v1 record. This is distinct from the existing
alignment-check.v1 (a governance-invariant structural check, no room for
Procrustes/residual data), so it is a dedicated schema. Classification per
framework 13.3: stable if residual < 0.05 and no critical feature drifted;
drifted if residual < 0.20 or <= 1 critical feature drifted; broken otherwise.
decision = aligned iff stable, else drift; the schema binds the two so a drift
record cannot claim alignment. Each record carries a SHA-256 (FIPS 180-4)
replay_seal over the canonical inputs and verdict.

Teeth both ways (make feature-alignment-ci, folded into make validate and
make lawful-learning-ci; schema teeth in make lawful-learning-schema-ci):
- near-identical space -> stable/aligned (residual 0.0052);
- rotated-but-equivalent space (40 deg rotation) -> aligned after Procrustes
  (residual 0.0), proving the alignment works, not raw distance;
- genuinely drifted space -> broken/drift, promotion blocked (residual 0.274,
  3 critical features drifted);
- malformed inputs (shape mismatch, non-finite) -> rejected;
- schema negatives: decision/classification mismatch, weak replay seal.

Runtime threshold calibration against live gemma-2-9b-it SAE activations and
approval-gate wiring are deferred follow-ups; fixtures are synthetic.

Refs #83, #82, #76.
@mdheller mdheller self-assigned this Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Procrustes feature-alignment / drift checker (framework 11 / 13.3)

1 participant