akm docs

akm 0.9.0 — milestone close-out plan

Status: PLANNED (2026-07-30). Owner ruling applied: all four open issues ship in 0.9.0.

The release sits at 0.9.0-rc.11 with the CLI overhaul (#734) merged and the open-items register recording its close-out state. This document is the implementation plan for the four issues remaining in the 0.9.0 milestone, in landing order.

Milestone triage

All five open issues across 0.9.0 and 0.9.1 were reviewed. An initial recommendation deferred #733 and #730 to 0.9.1; the owner ruled both stay in 0.9.0. Final state:

Issue Milestone Scope in one line
#692 salience boost 0.9.0 Remove the R2 contributor from default ranking; the removal also deletes a confirmed 5 s hot-path stall.
#672 repositories + guard 0.9.0 Move salience/outcome SQL behind src/storage/repositories/; add a raw-SQL lint rule.
#733 orphan-GC 0.9.0 One maintenance pass, one additive migration, one event type, one config gate. Lean by design — see Workstream C.
#730 OKF v0.2 0.9.0 Read v0.2 frontmatter; stamp generated/verified on AKM-native writes via proposals; conformance updated. The okf adapter stays consumer-only.
#652 write provenance 0.9.1 Unchanged — already re-scoped by the rc-5 audit to attribution/concurrency polish.

Sequencing note: A and B are small and touch the same salience surface — land them first (A deletes a raw-SQL site B would otherwise relocate). C and D are independent of each other and of A/B, and can proceed in parallel after.

Workstream A — #692: remove the salience boost from default ranking

Decision: fix option 1 (removal), the issue's stated preference. The R2 contributor multiplies search/curate scores by up to 1.2× from a signal that is retrieval-dominated (w_r = 0.60, src/commands/improve/salience.ts:356-360), warm-starts non-zero with no outcome evidence (salience.ts:293-297), has zero pack coverage, and measures as noise on live data (max ×1.071). No measured curate-bench delta justifies it, and STABILITY.md:308 classifies ranking as a tuning target — removal in 0.9.0 is contractually clean.

No config gate is added. A config key for a term being removed would be dead surface that the 1.0 freeze would then have to carry. If a future curate-golden-bench delta proves the signal out, reintroduction behind a default-off gate happens then (the contributor stays exported for exactly that experiment). rank_score itself is untouched — it remains the improve-internal selection signal (proactive/high-salience lanes, forgetting safety).

The removal also deletes the hot-path defect confirmed in the rc-5 audit as a side effect rather than tuning around it: today every default search acquires the maintenance activity synchronously — up to a 5 s spin (src/core/maintenance-barrier.ts:91-107) plus a lock-file create — before the 250 ms SQLite busy-timeout even applies (src/indexer/search/ranking.ts:102).

Changes

  1. src/indexer/search/ranking-contributors.ts:551-554 — drop salienceRankingContributor from defaultUtilityRankingContributors (the list becomes [utilityRankingContributor]). The contributor and its constants stay exported.
  2. src/indexer/search/ranking.ts — delete loadSalienceRankScores (:96-139) and the :263 default fallback. Its only production caller is that fallback; tests are the only other importers. New semantics for RankEntriesOptions.salienceRankScores: a Map applies the contributor explicitly (tests/evals); null/undefined mean off. Update the option's doc comment (:58-64) accordingly.
  3. With the loader gone, the search path holds no maintenance-barrier acquisition and opens no state.db handle. db-search.ts:502-511 needs no edit beyond whatever the option-type change forces.
  4. Remove the now-dead barrier import from ranking.ts:6 and confirm the search layer has no remaining maintenance-barrier dependency.

Tests

Acceptance

Workstream B — #672: salience/outcome repositories + widened SQL guard

Part 1 of the issue (prep-stage accumulator decomposition) shipped at 028cc3de. This lands the remaining part 2, with two corrections to the issue text discovered against the tree:

Changes

  1. src/storage/repositories/salience-repository.ts — verbatim moves from src/commands/improve/salience.ts: AssetSalienceRow (:380-393), upsertAssetSalience (:425-458), getAssetSalience (:463-473), getAllRankScores (:484-494), recordNoOp (:509-514), resetConsecutiveNoOps (:520-525), getConsecutiveNoOps (:531-535). Add getTopRetrievalSalience(db, limit) absorbing the health read (src/commands/health/metrics.ts:246-251).
  2. src/storage/repositories/outcome-repository.ts — verbatim moves from src/commands/improve/outcome-loop.ts: the upsert SQL inside updateAssetOutcome (:245-268; the domain function stays put and calls the repository), getAssetOutcome (:278-288), getAllAssetOutcomes (:293-302), getOutcomeScoresByRef (:308-323).
  3. Re-export the moved functions from improve/salience.ts and improve/outcome-loop.ts per the established precedent, so the six improve importers and eleven test files do not churn in this PR.
  4. src/commands/health/metrics.ts — replace the inline SELECT retrieval_salience … with the repository call; the withStateDb handle it already holds is passed through.
  5. The fifth raw-SQL site (ranking.ts:122) is deleted by Workstream A; if A has not merged first, its chunked select moves behind salience-repository.ts instead.
  6. scripts/lint-repository-sql.ts — add a third rule, state-table-sql: flag raw SQL naming asset_salience or asset_outcome anywhere under src/ except src/storage/repositories/ and src/core/state/migrations.ts. Per-rule scoping (the existing two rules keep their current GUARDED_PREFIXES; the new rule carries its own scope/exclusions). Workstream C's pass consumes the repositories, so the rule holds at zero from day one.

Tests

Acceptance

Workstream C — #733: orphan-GC pass (lean)

Deliberately minimal: one maintenance pass, one additive migration, one event type, one config gate. No quarantine archive, no circuit breaker, no health-advisory plumbing, no new tables. The issue's design constraints are each satisfied by something that already exists or by the smallest possible addition:

Scope cut: usage_events is excluded. Attached rows (entry_id set) already cascade on entry deletion (deleteUsageEventsByEntryIds, src/storage/repositories/index-entries-repository.ts:607-618), and the 90-day retention purge (purgeOldUsageEvents, src/indexer/usage/usage-events.ts:141) bounds detached remnants. If live data shows that purge isn't reaching them, extending the pass is a follow-up.

Changes

  1. Migration 021-asset-state-missing-since in src/core/state/migrations.ts — the two ALTER TABLE … ADD COLUMN statements, nothing else.
  2. runOrphanStateGcPass(ctx) in src/commands/improve/loop-stages.ts, registered in the maintenance sequence at :1005-1036 next to runOrphanProposalPurgePass (:1303), which it copies structurally (same {count, warnings} return, same borrowed-connection handling via withStateDb(…, { borrowed }) — the #585 pattern at :1400). Per run: a. Collect distinct refs from both tables (repository calls — Workstream B's modules), resolve them against entries.item_ref with the same chunked-probe shape the rekey script uses (scripts/rekey-asset-ref.ts:278), reusing the existing legacy-spelling normalization from preparation.ts:2153-2163 so a live asset keyed under an old spelling is never a candidate. b. Unresolved and missing_since IS NULL → stamp now. Resolved and missing_since set → clear it (the asset came back). c. If improve.stateGc.collect is true: delete rows whose missing_since is older than the grace constant, per-table, in one transaction each. d. Emit asset_state_gc with {pending, collected, byTable} when either is nonzero; surface the same counts in the pass summary line like the neighboring purge passes do.
  3. Config: stateGc: { collect: boolean } added to ImproveConfigSchema (src/core/config/schema/improve.ts), default false; JSON schema regenerated via scripts/gen-config-schema.ts.

Tests

One integration suite, tests/integration/commands/improve/state-gc.test.ts:

Acceptance

Workstream D — #730: OKF v0.2

Three parts: read-side adapter support, write-side provenance stamping through the proposal path, and the conformance/spec updates. One scope decision up front: the okf adapter stays consumer-only. AKM markdown is already an OKF-compatible superset (okf-support.md progressive-enhancement contract), so the new provenance fields are written only to AKM-native assets; runbook §10's write-rejection contract and the tests at okf-conformance.test.ts:832-936 are unchanged.

D1 — read side (src/core/adapter/adapters/okf-adapter.ts)

Today recognize (:125-171) reads exactly five keys (type, title, description, tags, timestamp); everything else folds into opaque documentJson. Add, staying lenient (never reject):

  1. updated ← generated.at, falling back to legacy timestamp (the v0.2 breaking change, with the fallback v0.2 itself allows).
  2. Parse the v0.2 families into new optional IndexDocument fields (src/core/adapter/types.ts:141-296): generated (by/at), verified (list; tolerate the single-mapping form), v0.2 sources (object list), status, stale_after, and okf_version (currently read nowhere in src/).
  3. Collision handling — three AKM fields already occupy adjacent names: sources?: string[] (wiki citations, silently drops non-strings at metadata.ts:486-489), generation?: number (consolidation depth), and quality: "generated". The v0.2 families therefore land under new, distinct field names (recommendation: a single namespaced provenance?: { sources, generatedBy, generatedAt, verified } plus flat lifecycleStatus / staleAfter), leaving the existing fields untouched. Final naming is an owner call at PR review; the plan only fixes that no existing field is overloaded.

D2 — write side (AKM-native assets via proposals)

The proposals system already tracks exactly what v0.2 wants on disk — source/sourceRun (PROV-DM modeled, proposal-types.ts:82-120), gateDecision, review — it just never leaves state.db. Project it at promotion time:

  1. promoteProposal stamps generated: { by, at } into the written asset's frontmatter, actor per the v0.2 convention (<producer>/<version> → akm/<pkg-version> for automated lanes; human:<id> when a human accepted the proposal).
  2. A proposal that passed its gate or review appends a verified: [{ by, at }] entry alongside.
  3. Where a proposal carries evidenceSources (types.ts:265-268), project them as v0.2 sources entries ({ resource } minimum).
  4. DOCUMENT_JSON_CARRIED_FIELDS (akm-adapter.ts:192-213) gains the new keys so AKM's own adapter rereads what it wrote; akm show surfaces them.

Deeper consumers — stale_after-driven re-verification, trust-tier ranking — are explicitly not 0.9.0 scope; they are the 0.9.x improve-tuning track.

D3 — conformance and spec docs

  1. Rewrite docs/architecture/testing/okf-v0.1-conformance-runbook.md for v0.2 and bump the pinned KC_REF (:57), inspecting the upstream okf/SPEC.md diff first as the runbook itself instructs (:61-62).
  2. Update the §0.1 OKF↔AKM field-mapping table in akm-0.9.0-bundle-adapter-spec.md (:22-35) and the frozen-reference paragraph (:480); add a v0.2 note to okf-support.md.
  3. Add the missing tests/fixtures/format-family-goldens/okf/ golden — OKF is the only registered adapter without one.

Tests

Acceptance

Sequencing and release

  1. PR 1 — Workstream A (behavior change; eval evidence + CHANGELOG).
  2. PR 2 — Workstream B (pure refactor + guard rule; rebases trivially).
  3. PR 3 — Workstream C and PR 4 — Workstream D in parallel (disjoint surfaces: state.db/maintenance vs adapter/proposal serialization). D is the largest; its one open naming decision (D1.3) should be settled in review, not up front.
  4. Cut the next rc with all four merged, soak, ship 0.9.0.
  5. On release: revisit milestone due dates (0.9.0's 2026-07-06 has passed; 0.9.1 — now carrying only #652 — is dated 2026-08-08), and schedule the two deliberate follow-ups: flipping improve.stateGc.collect on once live reports prove clean, and the deeper v0.2 consumers on the improve-tuning track.