← all docs · clause-taxonomy-normalization20 July 2026

Clause taxonomy normalization — implementation plan

Status: Planned
Author: Claude Opus 4.8
Date: 2026-07-20
Repo: north-os
Related: design spec; Cowork-demo remediation

Summary

Introduce a corpus-derived canonical taxonomy beside raw classifier labels, populate it safely, and make both typed-clause handlers retrieve by concept. Preserve exact-label rollout fallback, surface honest playbook neighbours, and expose extractor-recorded party-role context without inferring a protected side.

Problem frame

Exact clause_type equality fragments typed retrieval across 1,714 clause labels and 488 playbook labels. The strongest precedent and the firm's recorded position can exist under an equivalent label while the tool returns a weak subset or a bare absence.

Assumptions

This plan was authored in autonomous pipeline mode. These implementation bets are explicit for downstream review.

Requirements

Scope boundaries

Deferred to follow-up work

Context and patterns

Key technical decisions

High-level data flow

This is directional guidance for review, not implementation specification.

classifier raw label ── normalize/alias ──► canonical column
         │                         unresolved label centroids
         └── preserved verbatim ◄──────────────┘

request free text ── resolve concept ──► canonical candidate set
                                      ├─► clause embedding ranking
                                      └─► exact playbook rules
                                           └─ empty: nearest rule-type centroids, labelled non-exact

Implementation units

U1. Canonical vocabulary and resolver

Goal: Add the single typed taxonomy source used by ingestion, backfill, measurement, and retrieval.

Requirements: R1, R4

Dependencies: None

Files:

Approach: Store the exact ordered 135-token vocabulary, reviewed alias map, normalization rules, and corpus-derivation date as the reproducible source artifact. Export a const tuple and union within @workspace/knowledge; wire metadata remains validated strings in the lower-level contracts package to avoid reversing package dependencies. Constrain new extraction to the canonical list while retaining raw output validation and a null result for unrelated low-confidence free text.

Execution note: Implement resolver behavior test-first.

Test scenarios:

Verification: All canonical outputs are members of the exported union and the list has 100–150 entries derived from observed labels.

U2. Expand schema and ingestion writes

Goal: Persist canonical values for clauses and playbook rules without breaking historical rows.

Requirements: R2, R5

Dependencies: U1

Files:

Approach: Add nullable text columns without a new canonical index in this PR; the current corpus is bounded and the widened query plan will be observed before a separate online index rollout is justified. Populate canonical values on new inserts through the resolver. Generate and review the Drizzle migration; do not add SQL data updates or alter policies.

Test scenarios:

Verification: Generated SQL contains only the two additive columns; RLS and GRANT statements are absent. The generated migration passes migration lint, journal monotonicity, and immutability checks, then applies successfully to the local database before roles are reprovisioned.

U3. Idempotent backfill and read-only measurement

Goal: Canonicalize historical data and provide repeatable candidate-coverage evidence.

Requirements: R3, R8

Dependencies: U1, U2

Files:

Approach: Expose separate dotenv-wrapped package commands for SELECT-only measurement and writes. The writable command requires OWNER_DATABASE_URL, an explicit organization allowlist, an owner/BYPASSRLS role probe, bounded per-organization transactions, and dry-run by default. Update only null canonical columns. Seed explicit alias assignments, then use existing vector averages for unresolved raw-label centroids against canonical centroids per organization. Persist a centroid result only when independently calibrated clause/rule minimum-similarity and runner-up-margin thresholds both pass; leave low-confidence rows null and report them as unresolved. This command is a safe no-op retry of the initial backfill, not a taxonomy-remapping mechanism.

Test scenarios:

Verification: Two consecutive local runs produce a zero-update second summary. The PR runs preproduction in SELECT-only projection mode; after deployment, an authorized operator supplies the owner DSN to persist the backfill. A vocabulary revision requires a separate dry-run remap workflow rather than overwriting prior non-null classifications.

U4. Concept-complete precedent retrieval and party context

Goal: Retrieve all canonical synonyms, retain exact rollout fallback, and surface extractor-recorded named-party context.

Requirements: R4, R5, R7

Dependencies: U1, U2

Files:

Approach: Match canonical type plus the resolved concept's reviewed raw aliases for null/unmapped rollout rows under the existing authorization joins, rank by the existing clause embedding, and return raw/canonical/party-role metadata. Include full candidate-set size separately from the limited result rows. Tool rendering identifies party_role as extractor-recorded named-party context, never direction, protection, favorability, or client side.

Execution note: Start with failing handler contract tests.

Test scenarios:

Verification: A count independent of LIMIT k reflects the full canonical candidate set, while embedding order still determines the returned rows.

U5. Honest playbook neighbour fallback

Goal: Return a firm position for the resolved concept or the closest recorded canonical positions with explicit absence framing through a discriminated exact/nearest/absent result contract.

Requirements: R4, R5, R6

Dependencies: U1, U2

Files:

Approach: Under forced firm-global scope, return canonical-exact rules together with null/unmapped rows whose raw labels are reviewed aliases of that concept. If resolution fails, try normalized raw exact matching. Only when neither canonical nor raw exact matches exist, compare the requested clause centroid to canonical rule-type centroids and return a bounded nearest set with similarity and actual type. Keep exact and nearest states distinct in the result and audit payload.

Execution note: Implement exact-precedence and honest-absence tests before changing the handler.

Test scenarios:

Verification: limitation_of_liability yields an exact canonical position if backfilled rules exist, otherwise measured neighbours such as indemnification/recourse remain explicitly non-exact.

U6. End-to-end verification and rollout state

Goal: Prove contracts, migration safety, candidate-coverage improvement, and documentation status before shipping.

Requirements: R2, R8

Dependencies: U1–U5

Files: all files changed by U1–U5; generated superpowers status/index views.

Test scenarios:

Verification: Smoke the resolver, handler SQL contracts, measurement script, and rendered agent messages; set spec/plan status through the status tool and rebuild the site.

System-wide impact

Risks and mitigations

RiskMitigation
Centroid assigns an adjacent but not synonymous subtypeExplicit aliases win; similarity and runner-up margin must clear calibrated thresholds; low-confidence rows stay null; raw labels remain visible. Initial-backfill retries are idempotent, while future taxonomy revisions require an explicit remap workflow.
Canonical columns are null during rolling deployExact normalized raw fallback remains in both handlers.
Nearest playbook rule is mistaken for firm policyDistinct result status and explicit non-exact rendering with actual matched type.
Party role is mistaken for protected/client sideName it extractor-recorded named-party context; do not infer direction, action, protection, filtering, or ranking.
Backfill bypasses RLS incorrectlyRequire owner DSN, make scope explicit, use updates only in the dedicated script, and never run writes against preproduction during verification.

Outcome measurement

Observed by taxonomy:measure in a read-only transaction against preproduction on 2026-07-20. Canonical columns are not deployed there yet, so “canonical” is the deterministic post-migration alias fallback lower bound; high-confidence centroid backfill may add long-tail labels.

Requested conceptClauses: old exact → canonicalPlaybook rules: old exact → canonicalChange
limitation_of_liability25 → 880 → 03.52× clause recall
indemnification765 → 77526 → 26+10 clauses
assignment818 → 83329 → 29+15 clauses
jury_trial_waiver9 → 230 → 12.56× clauses; exact canonical playbook position recovered

limitation_of_liability has no exact canonical playbook rule. The measurement returns explicitly non-exact neighbours: indemnification (26 rules, cosine similarity 0.6701), recourse_carve_out (4, 0.5976), and insurance (27, 0.5619). Persisted post-backfill counts remain a post-deploy operational check after authorized owner-run execution.

Sources and references