Skip to content

feat(guardrails): anti-pattern K — SSOT sync drift (migration changed without mirroring the SSOT / version pins) #288

Closed

Description

@terisuke

Summary

New guardrail pattern beyond the existing A–J set: SSOT sync drift. An implementation changes one authoritative artifact (migration, contract, version pin) but forgets the mirror(s) that must end in the same final state. The existing CI contract check correctly fails — but only at PR time, after the implementation already shipped the half-change.

Trigger incident (2026-08-03, Grift PR anomalyco#1761)

Issue anomalyco#1719 asked to drop the now-unused index idx_conversation_turns_case (L-1). The implementer:

  • ✅ Added migration 000060_drop_idx_conversation_turns_case.up.sql (DROP INDEX)
  • ❌ Left packages/contracts/initial-schema.sql (SSOT) still CREATE-ing the same index
  • ❌ Left the Alpha expected post-migration version pins at 59 across 4 files (grift-deployment-policies.json, alpha_migration_preflight.go, check-v2-alpha-deployment-contract.mjs, v2-alpha-cd.yml)

Result: contract-validation (migrate-built DB vs initial-schema-built DB) failed correctly. The pipeline worked as designed; the implementation was half-synced.

Why this is NOT covered by A–J

Pattern Why not
A (impact/removal) removal-guard blocks deletions that HAVE reverse references. This index had ZERO readers — that is exactly why it was dropped. No reference search can find the SSOT mirror because the mirror is not a "reference"; it is a duplicate definition.
F (falsification) Not about test quality. The repo even has the check; the implementer just did not run it locally before pushing.
J (CI over-application) Opposite: CI under-application is not the issue — the check exists and fired; the issue is local pre-PR enforcement.
B/C/D/E/G/H/I Unrelated.

The pattern (generalized)

When a change makes two or more artifacts that must agree on the SAME final state (schema DDL vs migrations, OpenAPI vs generated code, version pins in workflow/policy/preflight), the implementer updates one side and leaves the other(s) stale.

Common shapes:

  • DB schema: migration DDL vs initial-schema.sql SSOT
  • API contract: openapi.yaml vs generated clients / server stubs
  • Version pins: a version appears in N files (workflow, policy JSON, Go preflight, JS contract check); bumping it in one place is missed in others
  • Replicated constants: the same value hardcoded in two languages

Proposed mechanism (mechanism over prose)

  1. CI wiring stays the source of truth — it already works. Add a local pre-push hook / command (/ssot-check or extend /plan-light) that runs the same "migrate-built vs SSOT-built" comparison the CI gate runs, so the drift fails locally instead of at PR time.
  2. Plugin hook (K): detect when a tool call modifies files under a "mirror set" (e.g., services/*/migrations/* OR packages/contracts/initial-schema.sql) and prompt: "this change touches a mirrored artifact — run the SSOT comparison now". Pure guidance, no block (avoid over-restriction, pattern I).
  3. Version-pin wiring test: a test that enumerates every file declaring the expected migration version (workflow, policy, preflight, contract check) and asserts they are equal — this is the F-style CI wiring test applied to K.
  4. Skill: ssot-sync skill (parallel to impact-analysis) — before changing a mirrored artifact, grep the mirror set, update all, and run the comparison.

Acceptance evidence (per pattern I, prove no over-restriction)

  • Negative: a migration change that ALSO updates the SSOT in the same commit passes with no warning.
  • Positive: a migration-only change triggers the prompt and fails the local comparison.
  • Falsification: disabling the guard allows the half-change through (existing CI still catches it, which is the safety net).

Scope

Fork-side packages/guardrails/profile/ plugin/command/skill + test. No upstream opencode changes required.

Refs

Activity

  1. terisuke commented on Aug 3, 2026

    @terisuke
    Author

    実装マージ済み: #289

    • Plugin ssot-guard.ts: mirror set 片側変更 → 助言のみ(ハードブロックなし = I)
    • /ssot-check + skill ssot-sync + plan-light / ADR にパターン K 追記
    • Version-pin 結線ヘルパー(等値アサート)
    • 受入: 陰性(migration+SSOT で助言なし)/ 陽性(migration-only で助言)/ 反証(OPENCODE_SSOT_GUARD=off)
    • テスト: ssot-guard.test.ts 13 pass、local:check green

    ローカル検証は entrypoint が .worktrees/cursor/ssot-sync-guard に pin されています。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions