# Release 0.4.5 — Graded quoting fallback and adjacent-vs-unrelated composition

**Coordinated packages:** `ds4-context-core`, `ds4-context-reference-adapter`, and `ds4-context-engine` 0.4.5.
**Implementation commit:** `91a700a`.

## Summary

Behavior and diagnostics patch. The compaction summarizer prompt no longer
forces an all-or-nothing choice between quoting a composite span and dropping the
bullet: it now tells the model to backtick only the fragments that are themselves
contiguous, and otherwise to keep the fact as ordinary text without backticks.
The diagnostics report separates `composed-adjacent-present` — the two parts sit
next to each other in one evidence source with nothing but joining punctuation
and spaces between them — from `composed-two-present-parts`, where the parts are
found at unrelated positions. Validation strictness, the eight-bullet and 25%
repair bounds, the 25% removal bound, the fail-closed fallback to Pi default
compaction, the SQLite schema and every provider-facing default are unchanged.

## Changes

- `packages/core/src/compaction/summary-contract.ts`:
  - `buildSummaryPrompt` replaces the single "omit the whole bullet" escape with a
    graded ladder: keep every backticked span one contiguous excerpt; when the
    complete value is not one contiguous excerpt, backtick only the fragments
    that are themselves contiguous with the joining text outside the backticks;
    if no fragment is contiguous on its own, write the value as ordinary text
    without backticks and keep the bullet; omit the bullet only when the fact
    itself has no support in the evidence. The prohibition on assembling a span
    from separately present values is unchanged and now carries the explicit
    counter-examples (`a setting name with its value`, `a path with a line
    range`, `a command with its flags`).
  - The diagnostics classifier adds the relation `composed-adjacent-present`. A
    composition is adjacent when one corpus source holds the two parts with at
    most the span's own separator length plus two characters between them, in a
    gap that contains no letters, digits or line breaks — so two parts on
    separate lines are not adjacent. Adjacency lookups are bounded
    (`MAX_ADJACENT_JOIN_PROBES` 8 per span, `MAX_JOIN_OCCURRENCES` 64
    occurrences per source) and cost the same budget unit as every other lookup.
  - `composed-two-present-parts` keeps its meaning for non-adjacent joins; both
    classes are class-only and observation-only.
- `tests/unit/summary-contract.test.ts`: prompt assertions follow the new
  wording, plus two cases that pin the sub-classification — a JSON-rendered pair
  (`{"compaction.model": "deepseek-flash"}` against
  `compaction.model: deepseek-flash`) reports `composed-adjacent-present`, and
  the same span against a source holding the two parts on separate lines reports
  `composed-two-present-parts`.
- `docs/COMPACTION.md`: the summary contract and the diagnostics section.
- `docs/ADR/066-quoting-downgrade-and-span-adjacency.md` records the decision.

## Why

The class-only report added in 0.4.2, made interpretable in 0.4.3 and re-budgeted
in 0.4.4, produced its first fully analysed production run: 14 rejected spans
across 14 bullets, `spansClassifiedCheap: 0`, `probesUsed: 11671` of 24000,
`classificationComplete: true`, `trigger: manual`.

| Relation | Count |
| --- | --- |
| `composed-two-present-parts` | 9 |
| `no-near-miss` | 2 |
| `single-deletion-present` | 2 |
| `not-classified-length` | 1 |

No rendering class remained, so ADR 065 exhausted the escape path. The dominant
residual cause is composition — the exact conduct the 0.4.1 prompt rule forbids —
and that rule was in the prompt this model ran with, so repeating the prohibition
was not the fix. The remaining hypothesis is the cost asymmetry: composing was
the only alternative to discarding a whole fact. This release removes that
asymmetry and, at the same time, adds the one measurement that decides whether a
safe acceptance rule can ever exist for composition: adjacent joins are
contiguous evidence regions the span re-rendered, while unrelated joins are
associations the evidence never contains, and only the first kind could be
accepted on the ADR 065 pattern.

The eight-bullet repair bound is deliberately untouched. It was the mechanism
that turned 14 affected bullets into a total fallback, and an explicit operator
decision on 2026-09-27 kept it: removing invalid bullets without a count limit
could silently delete a large share of a summary, and with these counts the 25%
removal bound would likely refuse as well, so the change would not reliably end
the fallback.

## Privacy and safety

- The report stays class-only. The new relation is one more fixed class name; no
  span text, fragment text, evidence text or hash of either is added to logs,
  warnings, or errors.
- The prompt change alters what the model is instructed to quote. It does not
  change what validation accepts, the repair bounds, the fail-closed decision, or
  the privacy path; no provider call, network access or configuration change is
  involved in this release.
- Fewer backticked spans is a possible outcome of the ladder. That is a rendering
  choice made by the summarizer, not a validator relaxation: an unsupported
  backticked value is still rejected exactly as before.

## Compatibility and persistence

- No migrations, no schema change (still 16), no new configuration key, and no
  change to any provider-facing default.
- The report shape is additive: `composed-adjacent-present` is a new key in
  `relations`, and existing readers keep working. A reader that previously saw
  only `composed-two-present-parts` will now see the adjacent cases split out.
- `chars-v1` remains the default estimator, `modelAwareness.autoTune` and DS4
  native continuation stay disabled, `storage.scope` keeps its `project` default.

## Measured scope and limitations

The effect on the observed fallback is not verified in a real session by this
release. The change is a prompt instruction, so the outcome depends on the
summarizer model's compliance: 0.4.1 already showed that a prohibition alone is
not followed. Expected observations for the next production compaction:

| Observation | Reading |
| --- | --- |
| `compaction.custom_fallback` absent | the graded fallback ended the composition |
| composition still present, mostly `composed-adjacent-present` | composition persists and a rendering-normalization rule is technically possible |
| composition still present, mostly `composed-two-present-parts` | composition persists and no acceptance rule can be safe; only the summarizer model, its reasoning level or the repair policy remain |
| fewer backticked values overall | the ladder worked, with the expected rendering cost in the summary |

## Validation

On Node 26.5.1 the release passed `npm run check` (99 Vitest files, 634 tests,
TypeScript builds and root typecheck), deterministic `npm run quality:compare`
(candidate `0.9875` against baseline `0.808156`), `npm run schema:context-persistence`
(`passed: true`), and `npm run pack:check` in a clean consumer for all three
packages at 0.4.5 (247 core files, 7 reference-adapter files, 104 extension
files). No provider calls are involved in this release procedure.

## Publication and registry verification

Published manually in dependency order from `2366c98`: `ds4-context-core@0.4.5`,
then `ds4-context-reference-adapter@0.4.5`, then `ds4-context-engine@0.4.5`, all
with the default `latest` tag, after `npm run pack:check` on the same commit.

`npm run registry:check -- 0.4.5`, run with a fresh npm cache, reported
"Verified all DS4 registry packages at exact version 0.4.5" on the first
attempt: all three packages install in a clean consumer, the exact
adapter/core dependencies resolve, the public core and KV exports import, the
compiled reference conformance and packaged quality corpus run, the
`ds4-context-storage` CLI shim responds, and the published Pi extension starts
against isolated offline RPC state. For all three packages
`npm view <package> dist-tags.latest` resolves to 0.4.5.

Annotated tag `v0.4.5` and the GitHub Release were created from `2366c98`. No
provider calls are involved in this release procedure; no session data,
credentials, or provider payloads were read or written while preparing it.
