opencode-swarm 7.148.0 → 7.148.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.
- package/dist/background/pr-review-collection-receipt.d.ts +2 -2
- package/dist/cli/{config-doctor-z2wam5wn.js → config-doctor-w6zphy4w.js} +2 -2
- package/dist/cli/{core-k7m76ce0.js → core-65qyqw2f.js} +1 -1
- package/dist/cli/{curation-policy-kd8j8cne.js → curation-policy-166wdjs4.js} +6 -6
- package/dist/cli/{curator-jwpm52kg.js → curator-2jhdfjqg.js} +27 -27
- package/dist/cli/{curator-llm-factory-4qt8b15s.js → curator-llm-factory-y43mx297.js} +27 -27
- package/dist/cli/{evidence-summary-service-90z295j3.js → evidence-summary-service-7qpganmn.js} +11 -11
- package/dist/cli/{gate-evidence-3q6q71fw.js → gate-evidence-p79xv25a.js} +6 -6
- package/dist/cli/{guardrail-explain-44xt373y.js → guardrail-explain-76sy950j.js} +28 -28
- package/dist/cli/{guardrail-log-kbp5jztb.js → guardrail-log-pm0w6ney.js} +3 -3
- package/dist/cli/{guardrail-reset-vpazhh8k.js → guardrail-reset-tkkg0bfz.js} +27 -27
- package/dist/cli/{hive-promoter-tdrkz9nh.js → hive-promoter-w4batwr6.js} +27 -27
- package/dist/cli/{index-jpa03jcc.js → index-2bsnwcmp.js} +1 -1
- package/dist/cli/{index-pgvx3rkq.js → index-2n9rwvcc.js} +1 -1
- package/dist/cli/{index-2y14mjfn.js → index-3yh2e5ca.js} +4 -2
- package/dist/cli/{index-9pmmexsd.js → index-5wypz451.js} +1 -1
- package/dist/cli/{index-x4rxh8j9.js → index-627mz27c.js} +3 -3
- package/dist/cli/{index-016708th.js → index-81n4ky69.js} +2 -2
- package/dist/cli/{index-52gnz782.js → index-c23yxr1s.js} +4 -4
- package/dist/cli/{index-d3bdr78h.js → index-ch6sjm8q.js} +1 -1
- package/dist/cli/{index-n96wwf56.js → index-dwg0z10h.js} +4 -4
- package/dist/cli/{index-sycndcwd.js → index-e9d2829t.js} +1 -1
- package/dist/cli/{index-h879g4r4.js → index-f115c8y6.js} +3 -3
- package/dist/cli/{index-y586zet4.js → index-fv5cc1rd.js} +2 -2
- package/dist/cli/{index-q81s0m8k.js → index-ghbkknjz.js} +79 -38
- package/dist/cli/{index-dmbaaz6d.js → index-hxqjf4me.js} +4 -4
- package/dist/cli/{index-zwpttdgn.js → index-jtnk7k6w.js} +1 -1
- package/dist/cli/{index-ffevkn7k.js → index-k5ze5v89.js} +1 -1
- package/dist/cli/{index-p62we2ar.js → index-kpgj4ha6.js} +1 -1
- package/dist/cli/{index-crpb0qke.js → index-kztxn1ww.js} +4034 -2834
- package/dist/cli/{index-qwj7qjjp.js → index-m97qwwrk.js} +2 -2
- package/dist/cli/{index-7ac5z627.js → index-mfhpk4c6.js} +1 -1
- package/dist/cli/{index-n0fpefqx.js → index-py2rsw1a.js} +4 -4
- package/dist/cli/{index-k6h5yc1j.js → index-q0gfmyzx.js} +5 -5
- package/dist/cli/{index-702zx3rk.js → index-qwzpsh3w.js} +1 -1
- package/dist/cli/{index-qcm97jfh.js → index-qzstn430.js} +1 -1
- package/dist/cli/{index-6q35swn2.js → index-rsn4jm90.js} +1 -1
- package/dist/cli/{index-dfvh1r8g.js → index-s3s2egzn.js} +29 -29
- package/dist/cli/{index-7hctrdk8.js → index-shazb5b6.js} +2 -2
- package/dist/cli/{index-rcjq8dm4.js → index-shm69en5.js} +2 -2
- package/dist/cli/{index-ykjjqg79.js → index-t6ys1386.js} +6 -6
- package/dist/cli/{index-gwrv6c56.js → index-w49v4xd6.js} +7 -7
- package/dist/cli/{index-07zgs6y0.js → index-x46mmaep.js} +14 -10
- package/dist/cli/index.js +27 -27
- package/dist/cli/{knowledge-escalator-0qnm48g8.js → knowledge-escalator-21n4ztqx.js} +11 -11
- package/dist/cli/{knowledge-events-3zv81nzp.js → knowledge-events-h5d5eayv.js} +9 -9
- package/dist/cli/{knowledge-link-w91bcyx2.js → knowledge-link-vgxe7gz9.js} +5 -5
- package/dist/cli/{knowledge-store-njtx6wj1.js → knowledge-store-y10m7x4f.js} +8 -6
- package/dist/cli/{knowledge-validator-7eqj5afv.js → knowledge-validator-dzm9sg0f.js} +8 -8
- package/dist/cli/{pending-delegations-k4x00mqc.js → pending-delegations-78zaaxz4.js} +3 -3
- package/dist/cli/{pr-subscriptions-x2mqc3f1.js → pr-subscriptions-9mg6mwc0.js} +3 -3
- package/dist/cli/{runner-gfzkp9nv.js → runner-rarzeth8.js} +6 -6
- package/dist/cli/{scan-cursor-a6dhyf55.js → scan-cursor-zn2qf06a.js} +7 -7
- package/dist/cli/{schema-p0vn1zgh.js → schema-ynwy7abm.js} +1 -1
- package/dist/cli/{scope-persistence-zs97zkt7.js → scope-persistence-sh30937s.js} +7 -7
- package/dist/cli/{skill-generator-5qg15q1j.js → skill-generator-r3knfwrp.js} +13 -13
- package/dist/cli/{telemetry-xzy0qeap.js → telemetry-1pnsftp0.js} +1 -1
- package/dist/cli/{worktree-collision-ownership-q2gswg16.js → worktree-collision-ownership-29md3k1n.js} +3 -3
- package/dist/cli/{worktree-isolation-ndb5fwys.js → worktree-isolation-yj7ssh24.js} +27 -27
- package/dist/consensus/contracts.d.ts +5 -1
- package/dist/consensus/corpus.d.ts +26 -3
- package/dist/consensus/miner.d.ts +5 -1
- package/dist/hooks/curator.d.ts +41 -1
- package/dist/hooks/knowledge-store.d.ts +26 -2
- package/dist/hooks/skill-scoring.d.ts +13 -3
- package/dist/hooks/skill-usage-log.d.ts +140 -17
- package/dist/hooks/skill-usage-pending.d.ts +392 -0
- package/dist/index.js +500 -499
- package/dist/observability/catalog.d.ts +6 -4
- package/dist/telemetry.d.ts +35 -1
- package/package.json +1 -1
|
@@ -3,9 +3,19 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Writes one JSONL line per skill-usage event to `.swarm/skill-usage.jsonl`.
|
|
5
5
|
* Follows the same append-only JSONL pattern as knowledge-application.jsonl.
|
|
6
|
+
*
|
|
7
|
+
* Issue #2038 — the JSONL is the **operational** stream and is bounded by a
|
|
8
|
+
* hard global byte/age/count budget (`SKILL_USAGE_LIMITS`). The
|
|
9
|
+
* **authoritative** record of un-consumed feedback lives in the sidecar
|
|
10
|
+
* `.swarm/skill-usage-pending.json` (see `skill-usage-pending.ts`), so
|
|
11
|
+
* evicting from this stream can lose no correctness signal: every actionable
|
|
12
|
+
* verdict is enqueued in the sidecar *before* it is appended here.
|
|
6
13
|
*/
|
|
7
14
|
import * as fs from 'node:fs';
|
|
8
15
|
import type { ConfidenceFloorOptions } from './knowledge-store.js';
|
|
16
|
+
import { bumpKnowledgeConfidenceBatchResult } from './knowledge-store.js';
|
|
17
|
+
import { enqueueSkillUsageFeedback, isSkillUsageQueueUnderPressure, readPendingManifest, type SkillUsageCoverage } from './skill-usage-pending.js';
|
|
18
|
+
export { isSkillWindowTrustworthy, SKILL_USAGE_LIMITS, type SkillUsageCoverage, type SkillUsagePendingRecord, } from './skill-usage-pending.js';
|
|
9
19
|
/** Single entry in the skill-usage audit log. */
|
|
10
20
|
export interface SkillUsageEntry {
|
|
11
21
|
/** Auto-generated unique identifier (UUID v4). */
|
|
@@ -51,9 +61,24 @@ export interface PruneResult {
|
|
|
51
61
|
pruned: number;
|
|
52
62
|
/** Number of entries remaining in the log. */
|
|
53
63
|
remaining: number;
|
|
54
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* Error message when the compaction could not be published; absent on
|
|
66
|
+
* success. Set both when the stream write/rename fails and when the
|
|
67
|
+
* manifest save that now precedes the rewrite fails (issue #2038 residual
|
|
68
|
+
* R1) — in the latter case nothing was dropped, so `pruned` is 0.
|
|
69
|
+
*/
|
|
55
70
|
error?: string;
|
|
56
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* What the window returned by a read can and cannot answer (issue #2038,
|
|
74
|
+
* requirement 4 / BLK-5). `complete === false` means entries the caller might
|
|
75
|
+
* have expected are not in `entries` — either this read was byte-truncated, or
|
|
76
|
+
* compaction has evicted history.
|
|
77
|
+
*/
|
|
78
|
+
export interface SkillUsageReadCoverage extends SkillUsageCoverage {
|
|
79
|
+
/** True when THIS read was bounded by `readMaxBytes` and saw only a suffix. */
|
|
80
|
+
truncatedRead: boolean;
|
|
81
|
+
}
|
|
57
82
|
/**
|
|
58
83
|
* Normalize a compliance verdict to the canonical spelling.
|
|
59
84
|
* The sole producer (`skill-propagation-gate.ts`) lowercases the regex
|
|
@@ -67,7 +92,8 @@ export declare function normalizeComplianceVerdict(verdict: string): string;
|
|
|
67
92
|
/**
|
|
68
93
|
* Test-only dependency-injection seam. Tests override these without
|
|
69
94
|
* `mock.module` (which leaks across files in Bun's shared test-runner).
|
|
70
|
-
* Restore in `afterEach
|
|
95
|
+
* Restore in `afterEach`, and call `_resetSkillUsageMaintenanceState()` to
|
|
96
|
+
* clear module-scoped maintenance counters.
|
|
71
97
|
*/
|
|
72
98
|
export declare const _internals: {
|
|
73
99
|
generateId: () => string;
|
|
@@ -81,33 +107,112 @@ export declare const _internals: {
|
|
|
81
107
|
openSync: typeof fs.openSync;
|
|
82
108
|
readSync: typeof fs.readSync;
|
|
83
109
|
closeSync: typeof fs.closeSync;
|
|
110
|
+
unlinkSync: typeof fs.unlinkSync;
|
|
84
111
|
pruneSkillUsageLog: typeof pruneSkillUsageLog;
|
|
85
112
|
resolveSourceKnowledgeIds: typeof resolveSourceKnowledgeIds;
|
|
86
113
|
applySkillUsageFeedback: typeof applySkillUsageFeedback;
|
|
114
|
+
/** Test seam: lets FB-008-style tests drive `failed:true` / partial-apply through the real consumption path without `mock.module`. */
|
|
115
|
+
bumpKnowledgeConfidenceBatchResult: typeof bumpKnowledgeConfidenceBatchResult;
|
|
87
116
|
parseGeneratedFromKnowledge: typeof parseGeneratedFromKnowledge;
|
|
88
117
|
computeComplianceByVersion: typeof computeComplianceByVersion;
|
|
89
118
|
normalizeComplianceVerdict: typeof normalizeComplianceVerdict;
|
|
90
|
-
readFeedbackAppliedEntryIds: typeof readFeedbackAppliedEntryIds;
|
|
91
119
|
appendFeedbackAppliedMarker: typeof appendFeedbackAppliedMarker;
|
|
120
|
+
/**
|
|
121
|
+
* Streaming read seam (issue #2038, BLK-11). The one-time migration and the
|
|
122
|
+
* compaction pass must see EVERY line of a legacy file — they are bounded in
|
|
123
|
+
* peak memory and per-read chunk size, never byte-truncated — so they cannot
|
|
124
|
+
* use `readFileSync` and cannot use the byte-bounded steady-state funnel.
|
|
125
|
+
*/
|
|
126
|
+
streamLogLines: typeof streamLogLines;
|
|
127
|
+
enqueueSkillUsageFeedback: typeof enqueueSkillUsageFeedback;
|
|
128
|
+
isQueueUnderPressure: typeof isSkillUsageQueueUnderPressure;
|
|
129
|
+
readPendingManifest: typeof readPendingManifest;
|
|
92
130
|
};
|
|
93
|
-
|
|
131
|
+
/**
|
|
132
|
+
* Reset the throttled-maintenance counters so an unswept run in Bun's shared
|
|
133
|
+
* test-runner process cannot shift a later test's first maintenance pass.
|
|
134
|
+
*/
|
|
135
|
+
export declare function _resetSkillUsageMaintenanceState(): void;
|
|
136
|
+
/**
|
|
137
|
+
* Legacy acknowledgment writer.
|
|
138
|
+
*
|
|
139
|
+
* Post-migration, acknowledgment is the sidecar's job: consumption dequeues
|
|
140
|
+
* the record instead of appending a marker line, which is what removes the
|
|
141
|
+
* unbounded marker accumulation described in issue #2038. This function is
|
|
142
|
+
* retained so that a pre-migration on-disk log written by an older build
|
|
143
|
+
* still round-trips through the reader above.
|
|
144
|
+
*/
|
|
94
145
|
declare function appendFeedbackAppliedMarker(directory: string, processedEntryIds: string[]): void;
|
|
146
|
+
/**
|
|
147
|
+
* Read `filePath` in `chunkBytes` slices and hand each complete, non-empty
|
|
148
|
+
* line to `onLine`. Peak resident memory is O(chunkBytes), independent of the
|
|
149
|
+
* file size — the metric requirement 4 actually cares about for the one-time
|
|
150
|
+
* migration and the compaction pass (BLK-11).
|
|
151
|
+
*
|
|
152
|
+
* A single line longer than 4x the chunk bound cannot be assembled without
|
|
153
|
+
* unbounded buffering; it is dropped and reported through `onOverlongLine` so
|
|
154
|
+
* the caller can fold it into the durable `corrupt` counter rather than
|
|
155
|
+
* losing it silently.
|
|
156
|
+
*/
|
|
157
|
+
declare function streamLogLines(filePath: string, chunkBytes: number, onLine: (line: string) => void, onOverlongLine?: () => void): void;
|
|
95
158
|
/**
|
|
96
159
|
* Validate and append a single skill-usage entry to the JSONL log.
|
|
97
160
|
*
|
|
98
161
|
* The `id` field is auto-generated; callers provide all other fields.
|
|
99
162
|
* Uses synchronous I/O for consistency with the JSONL append pattern.
|
|
163
|
+
*
|
|
164
|
+
* Two behaviors matter for issue #2038:
|
|
165
|
+
*
|
|
166
|
+
* 1. **Actionable verdicts are enqueued FIRST, then appended** (approved plan
|
|
167
|
+
* §2.2). A crash between the two leaves at worst an orphan queue record
|
|
168
|
+
* with no stats row — harmless, the record is self-sufficient. The reverse
|
|
169
|
+
* order leaves an authoritative gap. A failed enqueue **aborts the append
|
|
170
|
+
* and propagates**; the sole actionable caller already handles a throw.
|
|
171
|
+
* 2. **The `not_checked` path is a hard no-op for the queue** — no lock, no
|
|
172
|
+
* queue read-modify-write. That keeps O(paths x queue) synchronous I/O out
|
|
173
|
+
* of the hot delegation loop, and it is safe because `not_checked` carries
|
|
174
|
+
* no correctness signal. Under queue pressure those optional appends stop
|
|
175
|
+
* entirely (requirement 5).
|
|
100
176
|
*/
|
|
101
177
|
export declare function appendSkillUsageEntry(directory: string, entry: Omit<SkillUsageEntry, 'id'>): void;
|
|
178
|
+
/**
|
|
179
|
+
* Read and parse skill-usage entries together with the coverage of the window
|
|
180
|
+
* they came from (issue #2038, BLK-5).
|
|
181
|
+
*
|
|
182
|
+
* Additive: `readSkillUsageEntries` remains a wrapper returning `entries`
|
|
183
|
+
* only, so the DI seams in `corpus.ts` / `curator.ts` that are typed as
|
|
184
|
+
* `typeof readSkillUsageEntries` keep working unchanged.
|
|
185
|
+
*
|
|
186
|
+
* Takes no lock and never migrates — reads must stay cheap and must never
|
|
187
|
+
* mutate the store.
|
|
188
|
+
*/
|
|
189
|
+
export declare function readSkillUsageEntriesWithCoverage(directory: string, options?: SkillUsageFilterOptions): {
|
|
190
|
+
entries: SkillUsageEntry[];
|
|
191
|
+
coverage: SkillUsageReadCoverage;
|
|
192
|
+
};
|
|
102
193
|
/**
|
|
103
194
|
* Read and parse skill-usage entries from the JSONL log, optionally filtered.
|
|
104
195
|
*
|
|
105
196
|
* Malformed lines are silently skipped (no throw). Returns an empty array
|
|
106
|
-
* if the log file does not exist.
|
|
197
|
+
* if the log file does not exist. Bounded by `SKILL_USAGE_LIMITS.readMaxBytes`;
|
|
198
|
+
* use {@link readSkillUsageEntriesWithCoverage} when the caller needs to know
|
|
199
|
+
* whether the window it received is complete.
|
|
107
200
|
*/
|
|
108
201
|
export declare function readSkillUsageEntries(directory: string, options?: SkillUsageFilterOptions): SkillUsageEntry[];
|
|
109
202
|
/** Default maximum bytes to read from the end of the log file. */
|
|
110
203
|
export declare const TAIL_BYTES_DEFAULT: number;
|
|
204
|
+
/**
|
|
205
|
+
* Ceiling for an explicitly requested tail size.
|
|
206
|
+
*
|
|
207
|
+
* Deliberately equal to `TAIL_BYTES_DEFAULT`: the tail reader exists to serve
|
|
208
|
+
* the delegation dedup/scoring window, which is specified as 64 KiB, so an
|
|
209
|
+
* oversized request is clamped rather than honored. Issue #2038 §5 required
|
|
210
|
+
* that this ceiling and the global read budget must not disagree *in silence* —
|
|
211
|
+
* they no longer do, because the bounded read funnel
|
|
212
|
+
* ({@link readSkillUsageEntriesWithCoverage}) is routed around this constant
|
|
213
|
+
* and bounds itself with `SKILL_USAGE_LIMITS.readMaxBytes`, reporting
|
|
214
|
+
* `coverage.truncatedRead` when it cuts.
|
|
215
|
+
*/
|
|
111
216
|
export declare const MAX_TAIL_BYTES: number;
|
|
112
217
|
/**
|
|
113
218
|
* Read the last `maxBytes` of the skill-usage JSONL log and parse matching
|
|
@@ -131,11 +236,24 @@ export interface VersionComplianceStats {
|
|
|
131
236
|
}
|
|
132
237
|
export declare function computeComplianceByVersion(entries: SkillUsageEntry[], skillPath: string): Map<number | undefined, VersionComplianceStats>;
|
|
133
238
|
/**
|
|
134
|
-
* Prune the skill-usage log
|
|
135
|
-
* per unique skillPath. Oldest entries beyond the limit are removed.
|
|
239
|
+
* Prune the skill-usage log.
|
|
136
240
|
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
241
|
+
* Two budgets are enforced, in this order:
|
|
242
|
+
* 1. the legacy per-skill FIFO (`maxEntriesPerSkill`, default 500), and
|
|
243
|
+
* 2. the **hard global** byte / age / count ceiling from `SKILL_USAGE_LIMITS`.
|
|
244
|
+
*
|
|
245
|
+
* **BLK-3 — the highest-risk detail of issue #2038.** The old body returned
|
|
246
|
+
* early with `if (pruned === 0) return ...` immediately after per-skill
|
|
247
|
+
* pruning, so in the reported scenario — thousands of distinct skills, every
|
|
248
|
+
* one of them under 500 entries — nothing was ever pruned, the file was never
|
|
249
|
+
* rewritten, and the 1 MiB trigger simply re-fired on every append. The global
|
|
250
|
+
* budget is now evaluated by `applyRetention` **before** any early return, and
|
|
251
|
+
* the rewrite decision below is taken on the union of both budgets plus the
|
|
252
|
+
* marker set. There is deliberately no `pruned === 0` short-circuit ahead of it.
|
|
253
|
+
*
|
|
254
|
+
* Writes atomically (temp file + rename) in **global timestamp order**
|
|
255
|
+
* (BLK-12) so a rewrite can never push a live session's recent entries out of
|
|
256
|
+
* the 64 KiB tail window that the delegation dedup preload depends on.
|
|
139
257
|
*
|
|
140
258
|
* @returns Stats about how many entries were pruned and how many remain.
|
|
141
259
|
*/
|
|
@@ -165,25 +283,30 @@ export declare function resolveSourceKnowledgeIds(directory: string, skillPath:
|
|
|
165
283
|
*/
|
|
166
284
|
declare function parseGeneratedFromKnowledge(content: string): string[];
|
|
167
285
|
/**
|
|
168
|
-
*
|
|
169
|
-
*
|
|
286
|
+
* Consume pending skill-usage feedback and apply confidence bumps/decays to
|
|
287
|
+
* the originating knowledge entries.
|
|
288
|
+
*
|
|
289
|
+
* Reads the **authoritative sidecar queue**, never the JSONL stream: the
|
|
290
|
+
* compliant/violated counts and the per-skill delta are computed from queue
|
|
291
|
+
* records only, so an entry evicted from the operational stream can never flip
|
|
292
|
+
* a pending record's delta sign (approved plan §2 corollary).
|
|
170
293
|
*
|
|
171
|
-
* For each unique skillPath with at least one
|
|
294
|
+
* For each unique skillPath with at least one queued actionable record:
|
|
172
295
|
* 1. Resolve source knowledge UUIDs from the skill's SKILL.md frontmatter.
|
|
173
|
-
* 2. Count compliant and violated
|
|
296
|
+
* 2. Count compliant and violated records for that skill.
|
|
174
297
|
* 3. Compute net delta: if compliant count > violation count → +0.05; else → -0.1.
|
|
175
|
-
* 4. Call `
|
|
298
|
+
* 4. Call `bumpKnowledgeConfidenceBatchResult` with the aggregated deltas.
|
|
299
|
+
* 5. Dequeue, retain-with-retry, or terminal-dequeue each record per §3.
|
|
176
300
|
*
|
|
177
301
|
* @param directory - Project root directory.
|
|
178
|
-
* @param options.sinceTimestamp - Optional ISO 8601 cutoff; only process
|
|
302
|
+
* @param options.sinceTimestamp - Optional ISO 8601 cutoff; only process records after this time.
|
|
179
303
|
* @returns Count of processed skills and total confidence bumps/decays applied.
|
|
180
304
|
*/
|
|
181
305
|
export declare function applySkillUsageFeedback(directory: string, options?: {
|
|
182
306
|
sinceTimestamp?: string;
|
|
183
|
-
/** G2: forwarded to
|
|
307
|
+
/** G2: forwarded to bumpKnowledgeConfidenceBatchResult. */
|
|
184
308
|
floorOptions?: ConfidenceFloorOptions;
|
|
185
309
|
}): Promise<{
|
|
186
310
|
processed: number;
|
|
187
311
|
bumps: number;
|
|
188
312
|
}>;
|
|
189
|
-
export {};
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill-usage pending sidecar — `.swarm/skill-usage-pending.json` (issue #2038).
|
|
3
|
+
*
|
|
4
|
+
* Two files back the skill-usage subsystem:
|
|
5
|
+
*
|
|
6
|
+
* 1. `.swarm/skill-usage.jsonl` — the **OPERATIONAL** stream. Pure JSONL usage
|
|
7
|
+
* entries, no manifest header line, freely evictable under the global
|
|
8
|
+
* byte/age/count budget.
|
|
9
|
+
* 2. `.swarm/skill-usage-pending.json` — this file, the **AUTHORITATIVE**
|
|
10
|
+
* sidecar. A single JSON document holding the pending-feedback queue *and*
|
|
11
|
+
* all manifest state: `{ version, migrated, records[], counters{}, coverage{} }`.
|
|
12
|
+
*
|
|
13
|
+
* The manifest lives here rather than in a JSONL header line so that
|
|
14
|
+
* `parseSkillUsageEntry` stays header-free and the tail reader and the full
|
|
15
|
+
* reader remain on one parser (approved plan §0).
|
|
16
|
+
*
|
|
17
|
+
* Evicting an entry from the JSONL can lose no correctness signal because
|
|
18
|
+
* every actionable verdict (`compliant` / `violated`) is enqueued here
|
|
19
|
+
* *before* it is appended to the stream (approved plan §2). The queue record
|
|
20
|
+
* is self-sufficient: consumption computes compliant/violated counts and the
|
|
21
|
+
* per-skill delta from queue records only, never from JSONL entries.
|
|
22
|
+
*
|
|
23
|
+
* All I/O is synchronous, matching `skill-usage-log.ts`. State lives only under
|
|
24
|
+
* `.swarm/` resolved from the injected `directory` (never `process.cwd()`).
|
|
25
|
+
*/
|
|
26
|
+
import * as fs from 'node:fs';
|
|
27
|
+
/**
|
|
28
|
+
* The hard global budget for the skill-usage subsystem.
|
|
29
|
+
*
|
|
30
|
+
* | Key | Justification |
|
|
31
|
+
* |---|---|
|
|
32
|
+
* | `version` | requirement 1 "versioned"; stored in the sidecar |
|
|
33
|
+
* | `maxEntries` | must exceed 500 so the per-skill 500-entry fixture still prunes nothing |
|
|
34
|
+
* | `maxBytes` | must exceed the ~100 KB 600-entry fixture |
|
|
35
|
+
* | `maxAgeMs` | the age budget |
|
|
36
|
+
* | `floorPerSkill` | >= `curatorMinSample`, so the guaranteed window can still authorize a retirement |
|
|
37
|
+
* | `curatorMinSample` | minimum per-skill sample before the curator may retire/revise |
|
|
38
|
+
* | `readMaxBytes` | >= `maxBytes` + slack, so a bounded file always reads complete |
|
|
39
|
+
* | `migrationChunkBytes` | **chunk/buffer bound, NOT a total-bytes cap** — migration is single-pass streaming and is never byte-truncated |
|
|
40
|
+
* | `headerMaxBytes` | sidecar manifest-scalar bound |
|
|
41
|
+
* | `queueMaxRecords` | requirement 1 applies to marker types too; an upper guard, NOT the binding cap (see below) |
|
|
42
|
+
* | `queueMaxBytes` | **the binding cap** (see below) |
|
|
43
|
+
* | `maxAttempts` | bounds transient-failure retention |
|
|
44
|
+
* | `checkInterval` | throttled maintenance cadence, mirrors `telemetry.ts` |
|
|
45
|
+
*
|
|
46
|
+
* **Which queue cap binds, and why (issue #2038 review).** The approved plan
|
|
47
|
+
* justified `queueMaxBytes` as "~5,000 x ~100 B". That estimate is wrong by a
|
|
48
|
+
* factor of two. Measured `recordBytes` (`JSON.stringify(record).length + 1`,
|
|
49
|
+
* which is what {@link queueByteSize} sums):
|
|
50
|
+
*
|
|
51
|
+
* | `skillPath` | bytes/record | `queueMaxBytes` binds at |
|
|
52
|
+
* |---|---|---|
|
|
53
|
+
* | `skill-x` (a test-length path) | 200 | 2,621 records |
|
|
54
|
+
* | `.claude/skills/writing-tests/SKILL.md` | 230 | 2,279 records |
|
|
55
|
+
* | `.claude/skills/engineering-conventions/SKILL.md` | 240 | 2,184 records |
|
|
56
|
+
*
|
|
57
|
+
* A 36-char UUID id and two ISO-8601 timestamps (`timestamp`, `enqueuedAt`)
|
|
58
|
+
* are already 88 B of values before any key name. `queueMaxRecords` = 5,000
|
|
59
|
+
* would need <= 104.9 B/record to be reachable, and 5,000 realistic records
|
|
60
|
+
* would need a ~1,123 KiB byte budget.
|
|
61
|
+
*
|
|
62
|
+
* So `queueMaxBytes` (512 KiB) binds first, at roughly **2,200-2,600
|
|
63
|
+
* records**, and `queueMaxRecords` (5,000) is **not reachable** in practice.
|
|
64
|
+
* That is deliberate and left as-is: both numbers are published in
|
|
65
|
+
* `docs/observability-retention-registry.md` and
|
|
66
|
+
* `scripts/retention-registry.data.ts`, the byte budget is the one requirement
|
|
67
|
+
* 1 actually cares about (unbounded *growth*), and the eviction ladder in
|
|
68
|
+
* {@link enforceQueueBounds} is driven by `overBudget()`, which ORs the two —
|
|
69
|
+
* so the ladder and every counted discard behave identically whichever cap
|
|
70
|
+
* trips. `queueMaxRecords` remains as a cardinality guard for the degenerate
|
|
71
|
+
* case of pathologically short records. Do not "fix" the record count by
|
|
72
|
+
* shrinking the record: every field it carries has a consumer in
|
|
73
|
+
* `applySkillUsageFeedback`.
|
|
74
|
+
*/
|
|
75
|
+
export declare const SKILL_USAGE_LIMITS: {
|
|
76
|
+
readonly version: 1;
|
|
77
|
+
readonly maxEntries: 5000;
|
|
78
|
+
readonly maxBytes: number;
|
|
79
|
+
readonly maxAgeMs: number;
|
|
80
|
+
readonly floorPerSkill: 20;
|
|
81
|
+
readonly curatorMinSample: 10;
|
|
82
|
+
readonly readMaxBytes: 1677722;
|
|
83
|
+
readonly migrationChunkBytes: number;
|
|
84
|
+
readonly headerMaxBytes: number;
|
|
85
|
+
readonly queueMaxRecords: 5000;
|
|
86
|
+
readonly queueMaxBytes: number;
|
|
87
|
+
readonly maxAttempts: 5;
|
|
88
|
+
readonly checkInterval: 50;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Stale-break window for `.swarm/skill-usage.lock`, mirroring
|
|
92
|
+
* `src/context-map/telemetry.ts`. The knowledge-store bump holds its own
|
|
93
|
+
* lock for at most 5 retries / 5 s, so it cannot push a consumption cycle
|
|
94
|
+
* past this window.
|
|
95
|
+
*/
|
|
96
|
+
export declare const SKILL_USAGE_LOCK_STALE_MS: number;
|
|
97
|
+
/** Lifecycle state of a queued feedback record. */
|
|
98
|
+
export type SkillUsagePendingState = 'pending' | 'in_flight' | 'uncertain';
|
|
99
|
+
/**
|
|
100
|
+
* Terminal outcomes. Every one of these DEQUEUES the record and increments a
|
|
101
|
+
* health counter; none of them increments `processed` / `bumps` on the
|
|
102
|
+
* `applySkillUsageFeedback` return value (approved plan §3, E7).
|
|
103
|
+
*/
|
|
104
|
+
export type SkillUsageTerminalOutcome = 'no_source_knowledge' | 'no_matching_knowledge' | 'bump_unrecoverable' | 'uncertain_expired';
|
|
105
|
+
/** A single queued actionable verdict awaiting a confidence bump. */
|
|
106
|
+
export interface SkillUsagePendingRecord {
|
|
107
|
+
/** Same id as the JSONL entry it mirrors — the dedupe key. */
|
|
108
|
+
id: string;
|
|
109
|
+
/**
|
|
110
|
+
* Canonical skill path — no `file:` prefix, forward slashes only — the
|
|
111
|
+
* same spelling written to the stream for this id (issue #2038 review,
|
|
112
|
+
* DEFECT 2). Both writers normalize through
|
|
113
|
+
* `skill-usage-log.ts` `canonicalSkillPath`, so a record and its stream row
|
|
114
|
+
* can never disagree; `applySkillUsageFeedback` groups on THIS field, so a
|
|
115
|
+
* disagreement would split one skill's feedback into two groups.
|
|
116
|
+
* Records migrated from a pre-fix sidecar may still carry a raw spelling
|
|
117
|
+
* until they drain; every consumer of this field strips `file:`
|
|
118
|
+
* idempotently, so both forms resolve.
|
|
119
|
+
*/
|
|
120
|
+
skillPath: string;
|
|
121
|
+
/** Actionable verdict only. */
|
|
122
|
+
verdict: 'compliant' | 'violated';
|
|
123
|
+
/** ISO 8601 timestamp of the usage event. */
|
|
124
|
+
timestamp: string;
|
|
125
|
+
/** ISO 8601 mint time — the reference for queue age bounds. */
|
|
126
|
+
enqueuedAt: string;
|
|
127
|
+
state: SkillUsagePendingState;
|
|
128
|
+
/** Transient-failure retry counter, bounded by `maxAttempts`. */
|
|
129
|
+
attempts: number;
|
|
130
|
+
/** ISO 8601 time the record was claimed; only set while `in_flight`. */
|
|
131
|
+
inFlightAt?: string;
|
|
132
|
+
}
|
|
133
|
+
/** Durable lifetime counters. Fixed key set — bounded cardinality by construction. */
|
|
134
|
+
export declare const SKILL_USAGE_COUNTER_KEYS: readonly ["accepted", "compacted", "dropped", "skills_dropped", "corrupt", "uncertain_expired", "pending_evicted", "no_source_knowledge", "no_matching_knowledge", "bump_retry", "bump_unrecoverable", "bump_applied_zero", "pressure", "curator_skipped"];
|
|
135
|
+
export type SkillUsageCounterKey = (typeof SKILL_USAGE_COUNTER_KEYS)[number];
|
|
136
|
+
/**
|
|
137
|
+
* What the retained JSONL window can and cannot answer.
|
|
138
|
+
*
|
|
139
|
+
* Per-skill coverage is DERIVED from these global facts plus the retained
|
|
140
|
+
* count for the skill (see {@link isSkillWindowTrustworthy}) rather than
|
|
141
|
+
* stored per skill — a per-skill map would be an unbounded key set, which is
|
|
142
|
+
* exactly the failure mode issue #2038 is about.
|
|
143
|
+
*/
|
|
144
|
+
export interface SkillUsageCoverage {
|
|
145
|
+
/** True when no entry has ever been evicted by compaction. */
|
|
146
|
+
complete: boolean;
|
|
147
|
+
/** Oldest retained entry timestamp at the last compaction, or null. */
|
|
148
|
+
oldestRetained: string | null;
|
|
149
|
+
/** Newest retained entry timestamp at the last compaction, or null. */
|
|
150
|
+
newestRetained: string | null;
|
|
151
|
+
/** Cumulative entries evicted by compaction. */
|
|
152
|
+
entriesDropped: number;
|
|
153
|
+
/** Cumulative skills dropped whole by the admit-by-most-recent-use step. */
|
|
154
|
+
skillsDropped: number;
|
|
155
|
+
/** The floor guarantee in force, so consumers do not hardcode it. */
|
|
156
|
+
floorPerSkill: number;
|
|
157
|
+
}
|
|
158
|
+
/** The whole sidecar document. */
|
|
159
|
+
export interface SkillUsagePendingDocument {
|
|
160
|
+
version: number;
|
|
161
|
+
/** False (or absent) means the legacy migration has not completed yet. */
|
|
162
|
+
migrated: boolean;
|
|
163
|
+
records: SkillUsagePendingRecord[];
|
|
164
|
+
counters: Record<SkillUsageCounterKey, number>;
|
|
165
|
+
coverage: SkillUsageCoverage;
|
|
166
|
+
}
|
|
167
|
+
/** An enqueue request — the caller supplies the id minted for the JSONL entry. */
|
|
168
|
+
export interface SkillUsageEnqueueInput {
|
|
169
|
+
id: string;
|
|
170
|
+
skillPath: string;
|
|
171
|
+
verdict: 'compliant' | 'violated';
|
|
172
|
+
timestamp: string;
|
|
173
|
+
}
|
|
174
|
+
/** Opaque handle for an acquired `.swarm/skill-usage.lock`. */
|
|
175
|
+
export interface SkillUsageLockHandle {
|
|
176
|
+
readonly lockPath: string;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Test-only dependency-injection seam. Tests override these without
|
|
180
|
+
* `mock.module` (which leaks across files in Bun's shared test-runner).
|
|
181
|
+
* Restore in `afterEach`, and call `_resetSkillUsagePendingState()` to clear
|
|
182
|
+
* the module-scoped pressure cache.
|
|
183
|
+
*/
|
|
184
|
+
export declare const _internals: {
|
|
185
|
+
existsSync: typeof fs.existsSync;
|
|
186
|
+
readFileSync: typeof fs.readFileSync;
|
|
187
|
+
writeFileSync: typeof fs.writeFileSync;
|
|
188
|
+
renameSync: typeof fs.renameSync;
|
|
189
|
+
mkdirSync: typeof fs.mkdirSync;
|
|
190
|
+
statSync: any;
|
|
191
|
+
openSync: typeof fs.openSync;
|
|
192
|
+
closeSync: typeof fs.closeSync;
|
|
193
|
+
unlinkSync: typeof fs.unlinkSync;
|
|
194
|
+
now: () => number;
|
|
195
|
+
emitHealth: (payload: SkillUsageHealthPayload) => void;
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* Reset module-scoped caches so an unswept run in Bun's shared test-runner
|
|
199
|
+
* process cannot shift a later test's first read or pressure decision.
|
|
200
|
+
*/
|
|
201
|
+
export declare function _resetSkillUsagePendingState(): void;
|
|
202
|
+
/** Absolute path to the authoritative sidecar. */
|
|
203
|
+
export declare function resolvePendingPath(directory: string): string;
|
|
204
|
+
/** Absolute path to the shared skill-usage lock file. */
|
|
205
|
+
export declare function resolveSkillUsageLockPath(directory: string): string;
|
|
206
|
+
/**
|
|
207
|
+
* Non-blocking lock acquisition. Returns `null` when the lock is held by a
|
|
208
|
+
* live holder — maintenance and consumption are then **skipped, never forced**.
|
|
209
|
+
* Injection still fails open because nothing here throws.
|
|
210
|
+
*/
|
|
211
|
+
export declare function acquireSkillUsageLock(directory: string): SkillUsageLockHandle | null;
|
|
212
|
+
/**
|
|
213
|
+
* Lock acquisition for the **enqueue** path, which is exempt from the
|
|
214
|
+
* skip-not-force rule (approved plan §2.3, §9): a failed enqueue must abort
|
|
215
|
+
* the append and propagate rather than silently drop a correctness signal.
|
|
216
|
+
* Holders only perform a handful of synchronous file operations, so a short
|
|
217
|
+
* bounded retry absorbs ordinary contention.
|
|
218
|
+
*/
|
|
219
|
+
export declare function acquireSkillUsageLockOrThrow(directory: string): SkillUsageLockHandle;
|
|
220
|
+
/** Release a lock acquired above. Never throws. */
|
|
221
|
+
export declare function releaseSkillUsageLock(handle: SkillUsageLockHandle): void;
|
|
222
|
+
/** A fresh, un-migrated document. */
|
|
223
|
+
export declare function createPendingDocument(): SkillUsagePendingDocument;
|
|
224
|
+
/** Result of loading the sidecar. */
|
|
225
|
+
export interface LoadPendingResult {
|
|
226
|
+
doc: SkillUsagePendingDocument;
|
|
227
|
+
/** True when a corrupt sidecar was renamed aside and a fresh one substituted. */
|
|
228
|
+
quarantined: boolean;
|
|
229
|
+
/** Absolute path the corrupt document was moved to, when quarantined. */
|
|
230
|
+
quarantinePath?: string;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Read the sidecar.
|
|
234
|
+
*
|
|
235
|
+
* A corrupt or oversized document is **quarantined** (renamed aside) and
|
|
236
|
+
* counted — never silently reset to `[]`. The replacement document carries
|
|
237
|
+
* `migrated: false`, which makes the next lock-taking touch rebuild the queue
|
|
238
|
+
* from whatever the JSONL stream still holds (approved plan §6, requirement 3).
|
|
239
|
+
*/
|
|
240
|
+
export declare function loadPendingDocument(directory: string): LoadPendingResult;
|
|
241
|
+
/**
|
|
242
|
+
* Atomically replace the sidecar (temp file + rename). Throws on failure so
|
|
243
|
+
* the enqueue path can abort its append and propagate (approved plan §2.3).
|
|
244
|
+
*/
|
|
245
|
+
export declare function savePendingDocument(directory: string, doc: SkillUsagePendingDocument): void;
|
|
246
|
+
/**
|
|
247
|
+
* Read just the manifest scalars a reader needs, using a `statSync`-keyed
|
|
248
|
+
* cache so the 8 steady-state read sites do not each re-parse the sidecar.
|
|
249
|
+
*/
|
|
250
|
+
export declare function readPendingManifest(directory: string): {
|
|
251
|
+
coverage: SkillUsageCoverage;
|
|
252
|
+
migrated: boolean;
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* Per-skill coverage rule (approved plan §8 / BLK-8), derived rather than
|
|
256
|
+
* stored so the key set stays bounded.
|
|
257
|
+
*
|
|
258
|
+
* A skill's retained window may be used for a retire/revise decision when:
|
|
259
|
+
* (i) global coverage is COMPLETE — nothing has ever been evicted, so the
|
|
260
|
+
* retained window IS the whole history and there is nothing for this
|
|
261
|
+
* gate to protect against; OR
|
|
262
|
+
* (ii) coverage is incomplete AND the sample is at least `curatorMinSample`
|
|
263
|
+
* AND the retained window is at least the most-recent `floorPerSkill`
|
|
264
|
+
* entries — retention guarantees each surviving skill
|
|
265
|
+
* `min(count, floorPerSkill)` most-recent entries, so
|
|
266
|
+
* `retained >= floorPerSkill` establishes the window's shape by
|
|
267
|
+
* construction.
|
|
268
|
+
*
|
|
269
|
+
* **Why the minimum-sample floor sits behind `coverage.complete` (issue #2038
|
|
270
|
+
* implementation review, F2).** An earlier revision tested the floor FIRST, so a
|
|
271
|
+
* skill with 3 uses and 3 violations was never retired even on a fully complete,
|
|
272
|
+
* untruncated window. That silently narrowed shipped #1770/#1822 behavior, where
|
|
273
|
+
* the retire trigger was `violationRate > 0.3` with no sample floor, and it had
|
|
274
|
+
* nothing to do with compaction coverage — which is all this gate exists to
|
|
275
|
+
* judge. On complete coverage the sample IS the truth, so the pre-existing
|
|
276
|
+
* behavior applies unchanged; the floor is a statement about a TRUNCATED window
|
|
277
|
+
* and only applies to one.
|
|
278
|
+
*
|
|
279
|
+
* Note that under the shipped constants (`floorPerSkill` 20 >= `curatorMinSample`
|
|
280
|
+
* 10) the explicit `curatorMinSample` test below is subsumed by the floor
|
|
281
|
+
* comparison. It is kept because the constants are independently tunable and a
|
|
282
|
+
* sidecar may carry an older, smaller `coverage.floorPerSkill`; do not read it as
|
|
283
|
+
* load-bearing at today's values.
|
|
284
|
+
*
|
|
285
|
+
* Returns false when the decision must be skipped (and counted).
|
|
286
|
+
*/
|
|
287
|
+
export declare function isSkillWindowTrustworthy(coverage: SkillUsageCoverage, retainedCount: number): boolean;
|
|
288
|
+
/** Total serialized size of the queue records. */
|
|
289
|
+
export declare function queueByteSize(doc: SkillUsagePendingDocument): number;
|
|
290
|
+
/**
|
|
291
|
+
* Apply the queue budget: age, record count, and byte size — to every record
|
|
292
|
+
* including `uncertain` ones. Every discard is counted; none is silent.
|
|
293
|
+
*/
|
|
294
|
+
export declare function enforceQueueBounds(doc: SkillUsagePendingDocument, nowMs?: number): void;
|
|
295
|
+
/**
|
|
296
|
+
* Whether optional (`not_checked`) usage appends must stop.
|
|
297
|
+
*
|
|
298
|
+
* Deliberately cheap: the hot delegation loop appends one entry per skill
|
|
299
|
+
* path, so this must not take the lock or parse the sidecar. It reuses the
|
|
300
|
+
* value computed by the last write and otherwise refreshes from a `statSync`
|
|
301
|
+
* at most once per {@link PRESSURE_CACHE_MS}.
|
|
302
|
+
*/
|
|
303
|
+
export declare function isSkillUsageQueueUnderPressure(directory: string): boolean;
|
|
304
|
+
/**
|
|
305
|
+
* Merge records into the document, deduped by `id` (approved plan BLK-10).
|
|
306
|
+
* Returns the number actually added.
|
|
307
|
+
*/
|
|
308
|
+
export declare function mergePendingRecords(doc: SkillUsagePendingDocument, incoming: SkillUsageEnqueueInput[], nowIso: string): number;
|
|
309
|
+
/**
|
|
310
|
+
* Enqueue one actionable verdict.
|
|
311
|
+
*
|
|
312
|
+
* **Must be called BEFORE the JSONL append** (approved plan §2.2): a crash
|
|
313
|
+
* between the two then leaves at worst an orphan queue record with no stats
|
|
314
|
+
* row — harmless, because the record is self-sufficient. The reverse order
|
|
315
|
+
* leaves an authoritative gap.
|
|
316
|
+
*
|
|
317
|
+
* **Throws** on lock failure or write failure so the caller aborts the append
|
|
318
|
+
* and propagates (§2.3). This is the one path exempt from the skip-not-force
|
|
319
|
+
* rule; the sole actionable caller already handles a throw.
|
|
320
|
+
*/
|
|
321
|
+
export declare function enqueueSkillUsageFeedback(directory: string, input: SkillUsageEnqueueInput): void;
|
|
322
|
+
/**
|
|
323
|
+
* Resolve claims abandoned by a crashed cycle.
|
|
324
|
+
*
|
|
325
|
+
* An `in_flight` record whose claim is older than the lock stale-break window
|
|
326
|
+
* becomes `uncertain`: it survives and stays visible, but is never replayed —
|
|
327
|
+
* satisfying both clauses of "survives ... and is consumed at most once".
|
|
328
|
+
* A claim younger than that window belongs to a cycle that may still be
|
|
329
|
+
* running in another process and is left alone.
|
|
330
|
+
*/
|
|
331
|
+
export declare function resolveStaleInFlight(doc: SkillUsagePendingDocument, nowMs?: number): number;
|
|
332
|
+
/** Records eligible for consumption. `in_flight` and `uncertain` are excluded. */
|
|
333
|
+
export declare function selectConsumableRecords(doc: SkillUsagePendingDocument, sinceTimestamp?: string): SkillUsagePendingRecord[];
|
|
334
|
+
/** Mark records claimed. Persist before releasing the lock. */
|
|
335
|
+
export declare function markRecordsInFlight(doc: SkillUsagePendingDocument, ids: Iterable<string>, nowIso?: string): void;
|
|
336
|
+
/** Normal-path dequeue after a bump that actually applied. No terminal counter. */
|
|
337
|
+
export declare function dequeueRecords(doc: SkillUsagePendingDocument, ids: Iterable<string>): number;
|
|
338
|
+
/**
|
|
339
|
+
* Terminal dequeue. Counted in `skill_usage_health` only — terminal outcomes
|
|
340
|
+
* never increment `processed` / `bumps` (approved plan §3, E7).
|
|
341
|
+
*/
|
|
342
|
+
export declare function applyTerminalOutcome(doc: SkillUsagePendingDocument, ids: Iterable<string>, outcome: SkillUsageTerminalOutcome): number;
|
|
343
|
+
/**
|
|
344
|
+
* Transient-failure handling: the record stays `pending` and its attempt
|
|
345
|
+
* counter advances. At `maxAttempts` it goes terminal `bump_unrecoverable`,
|
|
346
|
+
* so a permanently-locked directory cannot retain records forever.
|
|
347
|
+
*/
|
|
348
|
+
export declare function retainWithRetry(doc: SkillUsagePendingDocument, ids: Iterable<string>): {
|
|
349
|
+
retried: string[];
|
|
350
|
+
unrecoverable: string[];
|
|
351
|
+
};
|
|
352
|
+
/**
|
|
353
|
+
* Payload shape of `telemetry.skillUsageHealth`. Counts only — **no
|
|
354
|
+
* `skillPath` and no per-skill identifier**: the adversarial case in issue
|
|
355
|
+
* #2038 is thousands of one-off skill IDs, so a per-skill label would be an
|
|
356
|
+
* unbounded label set and nothing in `check-event-contract.ts` would catch it.
|
|
357
|
+
*/
|
|
358
|
+
export interface SkillUsageHealthPayload {
|
|
359
|
+
trigger: 'compaction' | 'migration' | 'consumption' | 'pressure';
|
|
360
|
+
accepted: number;
|
|
361
|
+
compacted: number;
|
|
362
|
+
dropped: number;
|
|
363
|
+
skills_dropped: number;
|
|
364
|
+
corrupt: number;
|
|
365
|
+
pending_retained: number;
|
|
366
|
+
uncertain_retained: number;
|
|
367
|
+
uncertain_expired: number;
|
|
368
|
+
/** Actionable records discarded by the queue budget (issue #2038 F3). */
|
|
369
|
+
pending_evicted: number;
|
|
370
|
+
no_source_knowledge: number;
|
|
371
|
+
no_matching_knowledge: number;
|
|
372
|
+
bump_retry: number;
|
|
373
|
+
bump_unrecoverable: number;
|
|
374
|
+
bump_applied_zero: number;
|
|
375
|
+
pressure: number;
|
|
376
|
+
curator_skipped: number;
|
|
377
|
+
bytes: number;
|
|
378
|
+
limit_bytes: number;
|
|
379
|
+
oldest_timestamp: string | null;
|
|
380
|
+
newest_timestamp: string | null;
|
|
381
|
+
coverage: boolean;
|
|
382
|
+
}
|
|
383
|
+
/** Build the health payload from durable counters plus live gauges. */
|
|
384
|
+
export declare function buildSkillUsageHealthPayload(doc: SkillUsagePendingDocument, trigger: SkillUsageHealthPayload['trigger'], gauges: {
|
|
385
|
+
bytes: number;
|
|
386
|
+
limitBytes: number;
|
|
387
|
+
}): SkillUsageHealthPayload;
|
|
388
|
+
/** Emit the health signal. Never throws — observability must not break a write. */
|
|
389
|
+
export declare function emitSkillUsageHealth(doc: SkillUsagePendingDocument, trigger: SkillUsageHealthPayload['trigger'], gauges: {
|
|
390
|
+
bytes: number;
|
|
391
|
+
limitBytes: number;
|
|
392
|
+
}): void;
|