feat(ledger): divergence readout — button B where button A is canon, shape history, and judged-shape recheck (#2793, milestone 294 step 7)
CI & Build / TypeScript typecheck (push) Failing after 2s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 28s
CI & Build / Python tests (push) Failing after 37s
CI & Build / Build & push image (push) Skipped

Every judgment now goes through one helper that remembers the fingerprint
judged (classified_sha) and writes a code_shape_events row; the sync writes
vanished / reappeared / drifted events and flags recheck_at when a body
moves under an instance/variant. The refresh flags diverges_from on shapes
new since the previous computation that sit where one canon dominates the
judged siblings of their directory+kind and were not proposed as that canon
(a first seed flags nothing); the write-path hint asks the same question
in-band for the shapes the hook names. list_shapes(flag=divergence|recheck),
shape_history(project_id, path, symbol) (read-only), coverage line/payload/
card carry divergent + recheck. Backup v8 carries the history. Plugin 0.1.36.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 23:13:39 -04:00
co-authored by Claude Fable 5
parent 386b27e422
commit d74a244b3a
17 changed files with 880 additions and 62 deletions
+34
View File
@@ -447,6 +447,17 @@ interface Coverage {
// agent's confirm, and the biggest repeats-with-no-canon families.
proposed?: number;
derive_groups?: DeriveGroup[];
// The divergence readout (#2793): button B where button A is canon, and
// judged shapes whose bodies moved since they were judged.
divergent?: number;
divergence?: Divergence[];
recheck?: number;
}
interface Divergence {
path: string;
symbol: string;
kind: string;
canon_snippet_id: number;
}
const coverage = ref<Coverage | null>(null);
@@ -775,6 +786,28 @@ async function confirmDelete() {
{{ g.label }} <span class="coverage-gap-count">×{{ g.size }}</span>
</span>
</div>
<div
v-if="coverage.divergent || coverage.recheck"
class="coverage-gaps"
title="Divergent: a new shape where one canon dominates its directory and that isn't proposed as that canon — button B where button A is canon. Recheck: a judged shape whose body changed since it was judged."
>
<span class="coverage-gaps-label">Divergence:</span>
<span
v-for="d in coverage.divergence || []"
:key="d.path + '::' + d.symbol"
class="coverage-gap-chip coverage-divergent"
:title="d.path + ' — canon here is snippet #' + d.canon_snippet_id"
>
{{ d.kind === 'css' ? '.' : '' }}{{ d.symbol }}
<span class="coverage-gap-count"> #{{ d.canon_snippet_id }}</span>
</span>
<span v-if="(coverage.divergent || 0) > (coverage.divergence?.length || 0)" class="coverage-gap-chip">
+{{ (coverage.divergent || 0) - (coverage.divergence?.length || 0) }} more
</span>
<span v-if="coverage.recheck" class="coverage-gap-chip">
{{ coverage.recheck }} to recheck
</span>
</div>
</template>
<p v-else class="coverage-empty">
Not measured yet Refresh reads the bound repo's definitions into
@@ -1329,6 +1362,7 @@ async function confirmDelete() {
font-size: 0.74rem;
}
.coverage-gap-count { opacity: 0.65; }
.coverage-divergent { border-color: var(--fs-warning); }
.coverage-empty {
margin: 0;
font-size: 0.8rem;