# Release 0.4.1 — Contiguous backticked spans in compaction summaries

**Coordinated packages:** `ds4-context-core`, `ds4-context-reference-adapter`, and `ds4-context-engine` 0.4.1.
**Implementation commit:** `18d5e19`.

## Summary

Patch release that adds one explicit rule to the compaction summarizer prompt:
each backticked span must be one contiguous excerpt copied as-is from the
segment source, and a span must never join separately supported values. The
deterministic validation is unchanged. No configuration, schema, contract or
provider-facing default changes.

## Changes

- `buildSummaryPrompt` states that a backticked span must be one contiguous
  excerpt and must not assemble a value from parts that occur separately in the
  evidence (a setting name plus its value, a path plus a line range, a command
  plus its flags), and that the bullet must be omitted when the literal span is
  unavailable. The segment, aggregate and update prompts all inherit the rule
  because `buildAggregateSummaryPrompt` delegates to `buildSummaryPrompt`.
- `tests/unit/summary-contract.test.ts` asserts the rule in both the segment and
  the aggregate prompt.
- `docs/COMPACTION.md` records the rule in the summary contract description.

## Why

Two production sessions on separate hosts failed closed to Pi default
compaction with `compaction.custom_fallback`, `unsupported-exact-value`,
`repair=too-many-bullets`, and 30/20 then 23/19 unsupported spans and affected
bullets. The bounded repair removes at most eight whole bullets, so nineteen
affected bullets are refused without attempting any removal.

`compaction.maxConcurrentSegments: 1` was tested before this change: the same
failure class and the same magnitude appeared with a single segment in flight,
so request concurrency was not the cause. The remaining local mechanism is span
composition. Validation accepts a backticked span only when it is a literal
substring of the segment's own sanitized source
(`unsupportedExactMatches` in `packages/core/src/compaction/summary-contract.ts`),
and the value the model sees is part of that source by construction
(`sourceText` in `src/pi-adapter/compaction-adapter.ts` includes the serialized
conversation). Composite forms therefore cannot validate, and the previous
wording forbade them only implicitly through the verbatim requirement.

## Compatibility and persistence

- Validation strictness is unchanged: the same literal-substring rule, the same
  eight-bullet and 25% repair bounds, the same fail-closed fallback to Pi
  default compaction. Only the instruction given to the summarizer changed.
- No SQLite migration, no schema change (still 16), no canonical Pi JSONL
  change, no privacy consent change, no portable runtime-adapter change.
- Provider-facing defaults are unchanged: `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. Expected observations for the next real
compaction, with `compaction.summary.thinking` deliberately left unchanged so
that the prompt is the only variable:

| Observation | Reading |
| --- | --- |
| `compaction.custom_fallback` absent | span composition was the cause |
| lower counts, still above eight | partial effect; the model reduces but does not eliminate |
| unchanged counts | composition hypothesis rejected; move to the summarizer model or reasoning level |

Lowering `compaction.segmentTargetTokens` is not expected to resolve this
counter on its own: the affected-bullet count scales with segment size while the
eight-bullet bound is absolute, and every segment in a batch must validate.

## Validation and publication

On Node 26.5.1 the release passed a clean `npm ci`, `npm run check` (99 Vitest
files, 615 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.1 (247 core files, 7
reference-adapter files, 99 extension files). Tarball review found no session,
`.pi`, database, JSONL or credential files, and `git diff --check` was clean.
The coordinated publication then proceeded in dependency order
(`ds4-context-core`, then `ds4-context-reference-adapter`, then
`ds4-context-engine`), each command reporting the exact 0.4.1 artifact.
Registry propagation lagged the publishes: the first
`npm run registry:check -- 0.4.1` failed with
`ETARGET ... No matching version found for ds4-context-core@0.4.1`, and
`ds4-context-engine` still resolved to 0.4.0 after core, reference adapter and
core again were visible. Once propagation completed, the repeated
`npm run registry:check -- 0.4.1` reported "Verified all DS4 registry packages
at exact version 0.4.1", and the `latest` dist-tag resolved to 0.4.1 for all
three packages. No provider calls are involved in this release procedure.
