dsh-context-compression-improved 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/.gitattributes +1 -0
  2. package/.github/workflows/ci.yml +39 -0
  3. package/CHANGELOG.ja.md +39 -0
  4. package/CHANGELOG.ko.md +39 -0
  5. package/CHANGELOG.md +135 -0
  6. package/CHANGELOG.zh.md +39 -0
  7. package/CONTRIBUTING.md +22 -0
  8. package/README.ja.md +104 -0
  9. package/README.ko.md +103 -0
  10. package/README.md +89 -12
  11. package/README.zh.md +87 -12
  12. package/SECURITY.md +18 -0
  13. package/THIRD_PARTY_NOTICES.md +7 -31
  14. package/docs/installation.ja.md +76 -0
  15. package/docs/installation.ko.md +76 -0
  16. package/docs/installation.md +76 -0
  17. package/docs/installation.zh.md +76 -0
  18. package/docs/repair-log.md +582 -0
  19. package/eslint.config.js +30 -0
  20. package/package.json +85 -82
  21. package/packages/selector/LICENSE +21 -0
  22. package/packages/selector/README.md +26 -0
  23. package/packages/selector/README.zh.md +26 -0
  24. package/packages/selector/THIRD_PARTY_NOTICES.md +38 -0
  25. package/packages/selector/docs/history-tool-call-working-set-spec.md +112 -0
  26. package/packages/selector/docs/native-tool-result-selector-spec.md +34 -0
  27. package/packages/selector/docs/subagent-cache-reuse-spec.md +46 -0
  28. package/packages/selector/lib/style.css +308 -0
  29. package/packages/selector/package.json +115 -0
  30. package/{screenshots.json → packages/selector/screenshots.json} +6 -6
  31. package/packages/selector/src/client/CompressionProfileControls.tsx +229 -0
  32. package/packages/selector/src/client/CompressionProfileSelector.module.css +170 -0
  33. package/packages/selector/src/client/CompressionProfileSelector.tsx +79 -0
  34. package/packages/selector/src/client/CustomPolicyEditor.tsx +216 -0
  35. package/packages/selector/src/client/EstimatorControls.tsx +281 -0
  36. package/packages/selector/src/client/decode.ts +49 -0
  37. package/packages/selector/src/client/index.ts +111 -0
  38. package/packages/selector/src/client/locales.ts +198 -0
  39. package/packages/selector/src/client/preset-options.ts +70 -0
  40. package/packages/selector/src/client/settings-section.tsx +126 -0
  41. package/packages/selector/src/css-modules.d.ts +6 -0
  42. package/packages/selector/src/deepseek-v4-tokenizer.ts +210 -0
  43. package/packages/selector/src/estimator-catalog.ts +104 -0
  44. package/packages/selector/src/index.ts +327 -0
  45. package/packages/selector/src/invariant.ts +113 -0
  46. package/packages/selector/src/preset-overlay.ts +567 -0
  47. package/packages/selector/src/profiles.ts +342 -0
  48. package/packages/selector/src/pruner/content.ts +188 -0
  49. package/packages/selector/src/pruner/session.ts +94 -0
  50. package/packages/selector/src/pruner/state.ts +43 -0
  51. package/packages/selector/src/pruner/tuning.ts +23 -0
  52. package/packages/selector/src/pruner/types.ts +60 -0
  53. package/packages/selector/src/pruner.ts +2144 -0
  54. package/packages/selector/src/runtime/adaptive-cost.ts +194 -0
  55. package/packages/selector/src/runtime/audit.ts +215 -0
  56. package/packages/selector/src/runtime/config.ts +613 -0
  57. package/packages/selector/src/runtime/custom-policy.ts +278 -0
  58. package/packages/selector/src/runtime/deepseek-official-pricing.ts +298 -0
  59. package/packages/selector/src/runtime/deepseek-v4-vision-tokens.ts +254 -0
  60. package/packages/selector/src/runtime/measurement.ts +403 -0
  61. package/packages/selector/src/runtime/reducers.ts +656 -0
  62. package/packages/selector/src/runtime/retrieve.ts +457 -0
  63. package/packages/selector/src/runtime/session-events.ts +17 -0
  64. package/packages/selector/src/runtime/tail-trim.ts +166 -0
  65. package/packages/selector/src/runtime/token-count.ts +72 -0
  66. package/packages/selector/src/runtime/tokenpilot/dedup.ts +81 -0
  67. package/packages/selector/src/runtime/tokenpilot/estimator.ts +183 -0
  68. package/packages/selector/src/runtime/tokenpilot/locator.ts +128 -0
  69. package/packages/selector/src/runtime/tokenpilot/read-state.ts +77 -0
  70. package/packages/selector/src/runtime/types.ts +309 -0
  71. package/packages/selector/src/runtime/value.ts +48 -0
  72. package/packages/selector/tests/auto-compact.client.spec.tsx +226 -0
  73. package/packages/selector/tests/built/client-artifact.spec.ts +51 -0
  74. package/packages/selector/tests/cache-prefix-audit.spec.ts +123 -0
  75. package/packages/selector/tests/code-skeleton.client.spec.ts +88 -0
  76. package/packages/selector/tests/custom-contract.client.spec.ts +202 -0
  77. package/packages/selector/tests/estimator-catalog.spec.ts +70 -0
  78. package/packages/selector/tests/estimator-channel.client.spec.tsx +247 -0
  79. package/packages/selector/tests/estimator-route-registration.host.spec.ts +176 -0
  80. package/packages/selector/tests/host-preset-overlay.host.spec.ts +204 -0
  81. package/packages/selector/tests/preset-options-write.client.spec.ts +181 -0
  82. package/packages/selector/tests/preset-overlay-loader.e2e.host.spec.ts +196 -0
  83. package/packages/selector/tests/preset-overlay.host.spec.ts +243 -0
  84. package/packages/selector/tests/profiles.client.spec.tsx +434 -0
  85. package/packages/selector/tests/public/package-contract.client.spec.ts +33 -0
  86. package/packages/selector/tests/runtime/adaptive-cost.spec.ts +167 -0
  87. package/packages/selector/tests/runtime/audit.spec.ts +129 -0
  88. package/packages/selector/tests/runtime/auto-compact-config.spec.ts +523 -0
  89. package/packages/selector/tests/runtime/code-skeleton.spec.ts +141 -0
  90. package/packages/selector/tests/runtime/deepseek-official-pricing.spec.ts +186 -0
  91. package/packages/selector/tests/runtime/deepseek-v4-tokenizer.spec.ts +122 -0
  92. package/packages/selector/tests/runtime/deepseek-v4-vision-tokens.spec.ts +122 -0
  93. package/packages/selector/tests/runtime/fixtures/profile-baseline.json +273 -0
  94. package/packages/selector/tests/runtime/fixtures/tokenizer-golden.json +106 -0
  95. package/packages/selector/tests/runtime/fixtures/vision-golden.json +459 -0
  96. package/packages/selector/tests/runtime/public/public-runtime.spec.ts +2531 -0
  97. package/packages/selector/tests/runtime/session-events.spec.ts +27 -0
  98. package/packages/selector/tests/runtime/tokenizer-golden.spec.ts +53 -0
  99. package/packages/selector/tests/runtime/tokenpilot/dedup.spec.ts +52 -0
  100. package/packages/selector/tests/runtime/tokenpilot/estimator.spec.ts +56 -0
  101. package/packages/selector/tests/runtime/tokenpilot/locator.spec.ts +76 -0
  102. package/packages/selector/tests/runtime/tokenpilot/profile-baseline.spec.ts +100 -0
  103. package/packages/selector/tests/runtime/tokenpilot/read-state.spec.ts +58 -0
  104. package/packages/selector/tests/runtime/value.spec.ts +23 -0
  105. package/packages/selector/tests/standing-generation.host.spec.ts +631 -0
  106. package/packages/selector/tests/subagent-cache-reuse.host.spec.ts +250 -0
  107. package/packages/selector/tests/support/cache-prefix-audit.ts +105 -0
  108. package/packages/selector/tests/support/mock-adapter.ts +37 -0
  109. package/packages/selector/tests/support/ui-primitives.tsx +34 -0
  110. package/packages/selector/tsconfig.json +11 -0
  111. package/packages/selector/tsdown.client.config.ts +102 -0
  112. package/packages/selector/tsdown.config.ts +20 -0
  113. package/pnpm-workspace.yaml +19 -0
  114. package/scripts/capture-profile-baseline.ts +80 -0
  115. package/scripts/generate-tokenizer-fixtures.py +81 -0
  116. package/scripts/generate-vision-fixtures.py +208 -0
  117. package/scripts/packed-components-smoke.ts +713 -0
  118. package/scripts/packed-install-e2e.ts +1072 -0
  119. package/scripts/verify-release.ts +300 -0
  120. package/tests/TEST_INVENTORY.md +42 -0
  121. package/tsconfig.base.json +18 -0
  122. package/tsconfig.json +7 -0
  123. package/tsconfig.scripts.json +13 -0
  124. package/tsconfig.tests.json +15 -0
  125. package/vitest.built.config.ts +9 -0
  126. package/vitest.config.ts +43 -0
  127. /package/{assets → packages/selector/assets}/deepseek-v4/LICENSE.DeepSeek-V4-Pro.txt +0 -0
  128. /package/{assets → packages/selector/assets}/deepseek-v4/manifest.json +0 -0
  129. /package/{assets → packages/selector/assets}/deepseek-v4/tokenizer.json +0 -0
  130. /package/{assets → packages/selector/assets}/deepseek-v4/tokenizer_config.json +0 -0
  131. /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/LICENSE.DeepSeek-V4-Flash-Vision-Exp.txt +0 -0
  132. /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/manifest.json +0 -0
  133. /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/tokenizer.json +0 -0
  134. /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/tokenizer_config.json +0 -0
  135. /package/{assets → packages/selector/assets}/screenshots/context-compression-selector-profiles.jpg +0 -0
  136. /package/{assets → packages/selector/assets}/screenshots/context-compression-selector-settings.png +0 -0
  137. /package/{cordis.patch.yml → packages/selector/cordis.patch.yml} +0 -0
  138. /package/{dsh.plugin.json → packages/selector/dsh.plugin.json} +0 -0
  139. /package/{lib → packages/selector/lib}/client.d.ts +0 -0
  140. /package/{lib → packages/selector/lib}/client.js +0 -0
  141. /package/{lib → packages/selector/lib}/config.js +0 -0
  142. /package/{lib → packages/selector/lib}/index.d.ts +0 -0
  143. /package/{lib → packages/selector/lib}/index.js +0 -0
  144. /package/{lib → packages/selector/lib}/invariant.d.ts +0 -0
  145. /package/{lib → packages/selector/lib}/invariant.js +0 -0
  146. /package/{lib → packages/selector/lib}/pruner.d.ts +0 -0
  147. /package/{lib → packages/selector/lib}/pruner.js +0 -0
  148. /package/{lib → packages/selector/lib}/tail-trim.js +0 -0
@@ -0,0 +1,194 @@
1
+ /** Conservative cost interval used by the Adaptive History gate. */
2
+
3
+ import type {
4
+ MeasuredTokenSurfaceNode,
5
+ TokenCount,
6
+ } from './measurement.ts'
7
+ import { decimalRateNanoUnits } from './deepseek-official-pricing.ts'
8
+
9
+ /** Inputs that conservatively bound one already-planned Adaptive History batch. */
10
+ export interface AdaptiveTokenBoundsInput {
11
+ /** Exact sum of the planned canonical replacements' net token reduction. */
12
+ readonly exactReclaimedTokens: number
13
+ /** Durable sequence number of the earliest message changed by the plan. */
14
+ readonly earliestChangedSeq: number
15
+ /** Official request-level prompt_tokens from the immediately preceding request. */
16
+ readonly previousPromptTokens: number
17
+ /** Tokenizer artifact revision carried by that request's exact provider key. */
18
+ readonly expectedTokenizerRevision: string
19
+ /** Tokenizer-first measurement of that exact preceding assembled request. */
20
+ readonly previousRequestMeasurement: TokenCount
21
+ /** Same-revision provider-tokenizer counts for the durable surface. */
22
+ readonly measuredNodes: readonly MeasuredTokenSurfaceNode[]
23
+ }
24
+
25
+ /** Available lower-benefit and upper-risk bounds for one History batch. */
26
+ export interface AvailableAdaptiveTokenBounds {
27
+ readonly kind: 'available'
28
+ readonly measurementKind: 'exact-tokenizer' | 'tokenizer-estimate'
29
+ /** D: conservative lower bound of input tokens removed by the plan. */
30
+ readonly reclaimedLowerBoundTokens: number
31
+ /** A: conservative upper bound of retained suffix tokens that may lose a hit. */
32
+ readonly affectedRetainedSuffixUpperBoundTokens: number
33
+ /** Known exact canonical prefix before the first changed durable sequence. */
34
+ readonly exactPrefixLowerBoundTokens: number
35
+ }
36
+
37
+ /** Available conservative bounds or an explicit reason they cannot be derived. */
38
+ export type AdaptiveTokenBounds = AvailableAdaptiveTokenBounds | {
39
+ readonly kind: 'unknown'
40
+ readonly reason: string
41
+ }
42
+
43
+ /**
44
+ * Bound Adaptive's benefit and cache-loss exposure without attributing the
45
+ * request-level cache split to individual messages. Removed tokens are not
46
+ * charged again as part of the retained suffix.
47
+ * @param input - exact planned reclaim, adjacent request measurement, and same-revision nodes.
48
+ * @returns Conservative removal/cache-risk bounds or an explicit unknown reason.
49
+ */
50
+ export function deriveAdaptiveTokenBounds(input: AdaptiveTokenBoundsInput): AdaptiveTokenBounds {
51
+ if (!isCount(input.exactReclaimedTokens) || input.exactReclaimedTokens === 0) {
52
+ return unknown('invalid-reclaimed-token-count')
53
+ }
54
+ if (!isCount(input.earliestChangedSeq)) return unknown('invalid-earliest-changed-seq')
55
+ if (!isCount(input.previousPromptTokens)) return unknown('invalid-previous-prompt-tokens')
56
+ if (input.expectedTokenizerRevision.length === 0) {
57
+ return unknown('expected-tokenizer-revision-unavailable')
58
+ }
59
+
60
+ const request = input.previousRequestMeasurement
61
+ let margin = 0
62
+ let measurementKind: AvailableAdaptiveTokenBounds['measurementKind']
63
+ if (request.kind === 'unavailable') return unknown('request-measurement-unavailable')
64
+ if (request.kind === 'exact-tokenizer') {
65
+ if (!isCount(request.tokens) || request.tokens !== input.previousPromptTokens) {
66
+ return unknown('exact-request-usage-mismatch')
67
+ }
68
+ if (request.tokenizerRevision !== input.expectedTokenizerRevision) {
69
+ return unknown('request-tokenizer-revision-mismatch')
70
+ }
71
+ measurementKind = request.kind
72
+ } else {
73
+ const calibration = request.calibration
74
+ if (calibration === undefined) return unknown('estimate-calibration-unavailable')
75
+ if (!isCount(request.tokens)
76
+ || !isCount(request.upperBoundTokens)
77
+ || request.upperBoundTokens < request.tokens
78
+ || input.previousPromptTokens > request.upperBoundTokens
79
+ || !isCount(calibration.sampleCount)
80
+ || !isCount(calibration.conservativeMarginTokens)) {
81
+ return unknown('invalid-estimate-calibration')
82
+ }
83
+ margin = calibration.conservativeMarginTokens
84
+ measurementKind = request.kind
85
+ }
86
+
87
+ const reclaimedLowerBoundTokens = Math.max(0, input.exactReclaimedTokens - margin)
88
+ if (reclaimedLowerBoundTokens === 0) return unknown('reclaim-not-positive-after-margin')
89
+
90
+ let identity: { readonly tokenizerId: string; readonly tokenizerRevision: string } | undefined
91
+ let exactPrefixLowerBoundTokens = 0
92
+ const seen = new Set<number>()
93
+ for (const node of input.measuredNodes) {
94
+ if (!isCount(node.seq) || seen.has(node.seq)) return unknown('invalid-measured-node-sequence')
95
+ seen.add(node.seq)
96
+ if (node.seq >= input.earliestChangedSeq || node.count.kind !== 'exact-tokenizer') continue
97
+ if (!isCount(node.count.tokens)) return unknown('invalid-exact-prefix-count')
98
+ if (node.count.tokenizerRevision !== input.expectedTokenizerRevision) {
99
+ return unknown('exact-prefix-tokenizer-revision-mismatch')
100
+ }
101
+ if (identity !== undefined
102
+ && (identity.tokenizerId !== node.count.tokenizerId
103
+ || identity.tokenizerRevision !== node.count.tokenizerRevision)) {
104
+ return unknown('exact-prefix-tokenizer-identity-mismatch')
105
+ }
106
+ identity ??= node.count
107
+ exactPrefixLowerBoundTokens += node.count.tokens
108
+ if (!isCount(exactPrefixLowerBoundTokens)) return unknown('exact-prefix-overflow')
109
+ }
110
+
111
+ const accounted = exactPrefixLowerBoundTokens + reclaimedLowerBoundTokens
112
+ if (!isCount(accounted) || accounted > input.previousPromptTokens) {
113
+ return unknown('adaptive-bounds-exceed-previous-prompt')
114
+ }
115
+ return {
116
+ kind: 'available',
117
+ measurementKind,
118
+ reclaimedLowerBoundTokens,
119
+ affectedRetainedSuffixUpperBoundTokens: input.previousPromptTokens - accounted,
120
+ exactPrefixLowerBoundTokens,
121
+ }
122
+ }
123
+
124
+ /** Conservative bounds, capacity authority, and applicable official input prices. */
125
+ export interface ConservativeAdaptiveDecisionInput {
126
+ readonly capacityPressure: boolean
127
+ readonly bounds: AdaptiveTokenBounds
128
+ /** Official price per one million cache-hit input tokens. */
129
+ readonly inputCacheHitRate?: string
130
+ /** Official price per one million cache-miss input tokens. */
131
+ readonly inputCacheMissRate?: string
132
+ /** Request-level hit total; caps, but never attributes, the affected hit exposure. */
133
+ readonly observedCacheReadTokens?: number
134
+ }
135
+
136
+ /** Authorization decision plus fixed-point comparison telemetry when evaluated. */
137
+ export type ConservativeAdaptiveDecision =
138
+ | { readonly allowHistory: true; readonly reason: 'capacity-override' }
139
+ | {
140
+ readonly allowHistory: boolean
141
+ readonly reason: string
142
+ readonly minimumRemovalValue?: string
143
+ readonly maximumCacheLossPenalty?: string
144
+ }
145
+
146
+ /**
147
+ * Allow routine History only when D*P_hit is strictly greater than A*(P_miss-P_hit).
148
+ * @param input - capacity state, token bounds, request hit cap, and official input rates.
149
+ * @returns Capacity override or a strict fixed-point conservative-cost decision.
150
+ */
151
+ export function decideConservativeAdaptive(
152
+ input: ConservativeAdaptiveDecisionInput,
153
+ ): ConservativeAdaptiveDecision {
154
+ if (input.capacityPressure) return { allowHistory: true, reason: 'capacity-override' }
155
+ if (input.bounds.kind === 'unknown') {
156
+ return { allowHistory: false, reason: input.bounds.reason }
157
+ }
158
+ if (input.inputCacheHitRate === undefined || input.inputCacheMissRate === undefined) {
159
+ return { allowHistory: false, reason: 'adaptive-unknown-price' }
160
+ }
161
+ const hit = decimalRateNanoUnits(input.inputCacheHitRate)
162
+ const miss = decimalRateNanoUnits(input.inputCacheMissRate)
163
+ if (hit === undefined || miss === undefined || miss < hit) {
164
+ return { allowHistory: false, reason: 'adaptive-unknown-price' }
165
+ }
166
+ if (input.observedCacheReadTokens !== undefined && !isCount(input.observedCacheReadTokens)) {
167
+ return { allowHistory: false, reason: 'adaptive-invalid-cache-telemetry' }
168
+ }
169
+ const affectedHitUpperBound = input.observedCacheReadTokens === undefined
170
+ ? input.bounds.affectedRetainedSuffixUpperBoundTokens
171
+ : Math.min(
172
+ input.bounds.affectedRetainedSuffixUpperBoundTokens,
173
+ input.observedCacheReadTokens,
174
+ )
175
+ const minimumRemovalValue = BigInt(input.bounds.reclaimedLowerBoundTokens) * hit
176
+ const maximumCacheLossPenalty = BigInt(affectedHitUpperBound)
177
+ * (miss - hit)
178
+ return {
179
+ allowHistory: minimumRemovalValue > maximumCacheLossPenalty,
180
+ reason: minimumRemovalValue > maximumCacheLossPenalty
181
+ ? 'cost-interval-clearly-favourable'
182
+ : 'cache-risk-not-clearly-paid-back',
183
+ minimumRemovalValue: minimumRemovalValue.toString(),
184
+ maximumCacheLossPenalty: maximumCacheLossPenalty.toString(),
185
+ }
186
+ }
187
+
188
+ function unknown(reason: string): AdaptiveTokenBounds {
189
+ return { kind: 'unknown', reason }
190
+ }
191
+
192
+ function isCount(value: number): boolean {
193
+ return Number.isSafeInteger(value) && value >= 0
194
+ }
@@ -0,0 +1,215 @@
1
+ /** Structured, content-free runtime audit records for context compression. */
2
+
3
+ import type {
4
+ CompressionPolicy,
5
+ CompressionProfile,
6
+ ContextCompressionSettings,
7
+ HistoryMode,
8
+ ResolvedConfig,
9
+ } from './types.ts'
10
+
11
+ /** Stable prefix used to locate one JSON audit record in Harness runtime logs. */
12
+ export const COMPRESSION_AUDIT_PREFIX = 'context-compression audit '
13
+
14
+ /** Compression primitive that committed one model-free surface rewrite. */
15
+ export type CompressionAuditComponent =
16
+ | 'fresh'
17
+ | 'aggregate'
18
+ | 'history'
19
+ | 'tail-trim'
20
+ | 'native-tool-result'
21
+ | 'dedupe'
22
+ | 'summary-locator'
23
+
24
+ /** Non-mutating outcome of evaluating one component at one runtime boundary. */
25
+ export type CompressionAuditEvaluationStatus = 'disabled' | 'skipped'
26
+
27
+ interface CompressionAuditBase {
28
+ readonly schemaVersion: 1
29
+ readonly sessionId: string
30
+ }
31
+
32
+ /** Complete settings and deployment config frozen on first runtime observation. */
33
+ export interface CompressionPolicyFrozenAuditRecord extends CompressionAuditBase {
34
+ readonly kind: 'policy-frozen'
35
+ readonly settingsSource: 'host-settings' | 'plugin-config-fallback'
36
+ /** Where the frozen Auto Compact threshold came from. */
37
+ readonly autoCompactThresholdSource?: 'generation-config' | 'host-settings' | 'schema-default'
38
+ /** Present when the stored document was invalid and the session froze losslessly. */
39
+ readonly settingsInvalidFallback?: 'lossless-off'
40
+ readonly settings: ContextCompressionSettings
41
+ readonly deploymentConfig: ResolvedConfig
42
+ }
43
+
44
+ /** Routed request facts captured alongside one policy resolution. */
45
+ export interface CompressionRouteAuditFact {
46
+ readonly provider: string
47
+ readonly model: string
48
+ readonly modality?: string
49
+ }
50
+
51
+ /** Bundled tokenizer identity backing exact counts for one route. */
52
+ export interface CompressionTokenizerAuditFact {
53
+ readonly repository: string
54
+ readonly revision: string
55
+ }
56
+
57
+ /** How the resolved History watermarks were derived for one Session. */
58
+ export interface CompressionCoordinationAuditFact {
59
+ /** Frozen Auto Compact threshold percent from the settings snapshot. */
60
+ readonly thresholdPercent: number
61
+ /** `A = floor(C × a)`; undefined when linkage did not resolve. */
62
+ readonly autoCompactTokens?: number
63
+ /** `D = floor(A × 0.875)`; undefined when linkage did not resolve. */
64
+ readonly microDeadlineTokens?: number
65
+ /**
66
+ * Whether History follows the Auto Compact watermark, explicit deployment
67
+ * overrides, a mix of both, Custom manual tokens, or the fixed preset.
68
+ */
69
+ readonly paramSource: 'auto-compact-linked' | 'deployment-override' | 'mixed' | 'custom-manual' | 'fixed-preset'
70
+ }
71
+
72
+ /** One effective policy resolution, including context-percent capacity when used. */
73
+ export interface CompressionPolicyResolvedAuditRecord extends CompressionAuditBase {
74
+ readonly kind: 'policy-resolved'
75
+ readonly policy: CompressionPolicy
76
+ readonly contextWindowTokens?: number
77
+ readonly coordination?: CompressionCoordinationAuditFact
78
+ readonly route?: CompressionRouteAuditFact
79
+ readonly tokenizer?: CompressionTokenizerAuditFact
80
+ }
81
+
82
+ /** One committed model-free surface rewrite and its exact token accounting. */
83
+ export interface CompressionRewriteAuditRecord extends CompressionAuditBase {
84
+ readonly kind: 'rewrite'
85
+ readonly profile: CompressionProfile
86
+ readonly component: CompressionAuditComponent
87
+ readonly stage: 'fresh' | 'pressure'
88
+ readonly reducer: string
89
+ readonly historyMode?: HistoryMode
90
+ readonly manifestEventType: 'compaction/prune'
91
+ readonly manifestSeq: number
92
+ readonly replacementSeq: number
93
+ readonly sourceSeqs: readonly number[]
94
+ readonly tokensBefore: number
95
+ readonly tokensAfter: number
96
+ readonly tokensRemoved: number
97
+ readonly tokenizerId: string
98
+ readonly tokenizerRevision: string
99
+ }
100
+
101
+ /** Why an enabled component did not rewrite, or why it was disabled. */
102
+ export interface CompressionComponentEvaluationAuditRecord extends CompressionAuditBase {
103
+ readonly kind: 'component-evaluation'
104
+ readonly profile: CompressionProfile
105
+ readonly component: CompressionAuditComponent
106
+ readonly stage: 'fresh' | 'pressure'
107
+ readonly status: CompressionAuditEvaluationStatus
108
+ readonly reason: string
109
+ readonly historyMode?: HistoryMode
110
+ readonly measurementKind?: 'exact-tokenizer' | 'tokenizer-estimate' | 'unavailable'
111
+ readonly currentTokens?: number
112
+ readonly triggerTokens?: number
113
+ readonly targetTokens?: number
114
+ /** Reclaim the planned batch reached (insufficient-reclaim family only). */
115
+ readonly reclaimTokens?: number
116
+ /** Reclaim the batch required (insufficient-reclaim family only). */
117
+ readonly requiredTokens?: number
118
+ }
119
+
120
+ /** One fail-open runtime error, without prompt or tool-result content. */
121
+ export interface CompressionFailureAuditRecord extends CompressionAuditBase {
122
+ readonly kind: 'failure'
123
+ readonly stage: 'fresh' | 'pressure'
124
+ readonly operation:
125
+ | 'request-boundary'
126
+ | 'terminal-pass'
127
+ | 'policy-resolution'
128
+ | 'summary-locator'
129
+ | 'publication'
130
+ readonly component?: CompressionAuditComponent
131
+ readonly manifestSeq?: number
132
+ readonly errorName: string
133
+ readonly errorMessage: string
134
+ }
135
+
136
+ /** One observed core Native auto-compaction summary event. */
137
+ export interface NativeAutoCompactAuditRecord extends CompressionAuditBase {
138
+ readonly kind: 'native-auto-compact'
139
+ readonly manifestEventType: 'compaction/summary'
140
+ readonly manifestSeq: number
141
+ readonly reducer: 'llm-summary'
142
+ readonly provider: string
143
+ readonly model: string
144
+ readonly tokensBefore: number | null
145
+ readonly tokensAfter: null
146
+ }
147
+
148
+ /** One Exact Sources locator block appended to a landed Auto Compact summary. */
149
+ export interface SummaryLocatorAuditRecord extends CompressionAuditBase {
150
+ readonly kind: 'summary-locator'
151
+ readonly profile: CompressionProfile
152
+ readonly checkpointSeq: number
153
+ readonly summarySeq: number
154
+ readonly locatorChars: number
155
+ readonly spillFiles: number
156
+ readonly touchedFiles: number
157
+ }
158
+
159
+ /** One background estimator pass. Only numeric metadata — never prompts or keys. */
160
+ export interface EstimatorOutcomeAuditRecord extends CompressionAuditBase {
161
+ readonly kind: 'estimator-outcome'
162
+ readonly profile: CompressionProfile
163
+ readonly channel: 'host' | 'direct'
164
+ readonly sampled: number
165
+ readonly expired: number
166
+ readonly latencyMs: number
167
+ readonly ok: boolean
168
+ }
169
+
170
+ /** Closed version-one context-compression audit vocabulary. */
171
+ export type CompressionAuditRecord =
172
+ | CompressionPolicyFrozenAuditRecord
173
+ | CompressionPolicyResolvedAuditRecord
174
+ | CompressionRewriteAuditRecord
175
+ | CompressionComponentEvaluationAuditRecord
176
+ | CompressionFailureAuditRecord
177
+ | NativeAutoCompactAuditRecord
178
+ | SummaryLocatorAuditRecord
179
+ | EstimatorOutcomeAuditRecord
180
+
181
+ /** Minimal logger method consumed by the audit publisher. */
182
+ export interface CompressionAuditLogger {
183
+ /**
184
+ * Publish one informational message.
185
+ * @param message - complete single-line audit message.
186
+ * @returns logger-specific chaining value, if any.
187
+ */
188
+ info(message: string): unknown
189
+ }
190
+
191
+ /**
192
+ * Encode one stable single-line audit message.
193
+ * @param record - content-free structured audit record.
194
+ * @returns the fixed prefix followed by one JSON object.
195
+ */
196
+ export function formatCompressionAudit(record: CompressionAuditRecord): string {
197
+ return `${COMPRESSION_AUDIT_PREFIX}${JSON.stringify(record)}`
198
+ }
199
+
200
+ /**
201
+ * Publish one audit message through the Harness logger.
202
+ * @param logger - current plugin logger.
203
+ * @param record - structured record committed by the caller.
204
+ */
205
+ export function emitCompressionAudit(
206
+ logger: CompressionAuditLogger,
207
+ record: CompressionAuditRecord,
208
+ ): void {
209
+ try {
210
+ logger.info(formatCompressionAudit(record))
211
+ }
212
+ catch {
213
+ // Swallow logger failures: audit publication must not roll back a committed rewrite.
214
+ }
215
+ }