Stage K-1: real kernel realloc; word-metrics defects closed (FABRIC-3.10.8 §4.1)

- Kernel sf_realloc() is a real realloc (copy min(old,new) via new
  kmalloc_usable_size(), free old, NULL-on-0 frees). It allocated fresh
  and nothing else: every growth leaked, and vm_state_push()'s call-state
  stack lost every saved state past depth 8 (found here).
- Shim realloc() copies min(old,new) (over-read when growing).
- Defect 9: FC bucket growth no longer leaks or patches a copy; no
  sf_xrealloc() (freed a block the index still held on failure).
- Defect 5: heat snapshot heap-sized (was heats[1024], truncating).
- Defect 6: word refused whole when its metrics can't be allocated.
- Defect 7: vm_cleanup() (both) frees metrics' inner arrays.
- Defect 8: most-likely-next kept incrementally; no full id scan.
- Probe (removed): realloc keeps contents, heap returns exactly; call
  stack depth 20 intact; all three ISAs. Churn criterion NOT met: ~7 KB
  retained per churned VM (quota slots/word rows -- K-4 re-measures); a
  post-churn CONSOLE-CMD refusal attributed to the quota leak, K-4 retests.
- Standard sequence on amd64/aarch64/riscv64: hashes, G, ledger unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-26 02:30:43 -04:00
co-authored by Claude Opus 5.5
parent ef849906d9
commit 27c430e0ca
22 changed files with 55619 additions and 65 deletions
+31 -17
View File
@@ -251,6 +251,20 @@ int transition_metrics_record(WordTransitionMetrics *metrics, uint32_t next_word
metrics->transition_heat[next_word_id]++;
metrics->total_transitions++;
/* FABRIC-3.10.8 K-1: keep the most-likely-next word current here, so
* transition_metrics_update_cache() needn't scan the whole id space on
* every transition. Counts only ever rise by one, so the leader changes
* only when this word overtakes it (ties: the lower id, as the scan
* picked). */
{
uint32_t best = metrics->most_likely_next_word_id;
uint64_t bc = (best < metrics->transition_heat_cap) ? metrics->transition_heat[best] : 0;
uint64_t nc = metrics->transition_heat[next_word_id];
if (nc > bc || (nc == bc && next_word_id < best)) {
metrics->most_likely_next_word_id = next_word_id;
}
}
return 0;
}
@@ -328,30 +342,30 @@ int64_t transition_metrics_get_probability_q48(const WordTransitionMetrics *metr
* must match the size used at allocation time.
*/
void transition_metrics_update_cache(WordTransitionMetrics *metrics, uint32_t dict_size) {
if (!metrics || !metrics->transition_heat || metrics->total_transitions == 0) {
if (!metrics) return; /* K-1: was dereferenced in the branch below when NULL */
if (!metrics->transition_heat || metrics->total_transitions == 0) {
metrics->max_transition_probability_q48 = 0;
metrics->most_likely_next_word_id = 0;
return;
}
int64_t max_prob_q48 = 0;
uint32_t max_idx = 0;
/* Find word with highest transition count. Bounded by the array's own
* length (S-1b): it may lag dict_size until the next grow. */
/* FABRIC-3.10.8 K-1: the leader (highest transition count, lowest id on
* a tie) is kept current by transition_metrics_record(); this only
* refreshes its probability. It used to scan every id up to dict_size
* on every transition. One difference, by construction: the scan
* compared probabilities, which are floored Q48.16 quotients, so once
* total_transitions passes 65536 two different counts could floor
* equal and the scan kept the lower id; the leader is by count.
* Bounded by the array's own length (S-1b): it may lag dict_size. */
uint32_t limit = (dict_size < metrics->transition_heat_cap) ? dict_size : metrics->transition_heat_cap;
for (uint32_t i = 0; i < limit; i++) {
if (metrics->transition_heat[i] > 0) {
int64_t prob_q48 = transition_metrics_get_probability_q48(metrics, i);
if (prob_q48 > max_prob_q48) {
max_prob_q48 = prob_q48;
max_idx = i;
}
}
}
uint32_t best = metrics->most_likely_next_word_id;
metrics->max_transition_probability_q48 = max_prob_q48;
metrics->most_likely_next_word_id = max_idx;
if (best >= limit || metrics->transition_heat[best] == 0) {
metrics->max_transition_probability_q48 = 0;
metrics->most_likely_next_word_id = 0;
return;
}
metrics->max_transition_probability_q48 = transition_metrics_get_probability_q48(metrics, best);
}
/**