pi-fold 1.0.4 → 3.0.0
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/README.md +75 -28
- package/extensions/active-context.ts +1213 -2756
- package/extensions/evidence.js +0 -10
- package/extensions/index.js +25 -32
- package/extensions/json.ts +3 -29
- package/extensions/lib/canonical.ts +0 -8
- package/extensions/lib/curation.ts +52 -313
- package/extensions/lib/editor-ui.ts +739 -0
- package/extensions/lib/folding.ts +57 -446
- package/extensions/lib/instrumentation.ts +13 -127
- package/extensions/lib/measurement.ts +6 -83
- package/extensions/lib/persistence.ts +272 -229
- package/extensions/lib/policy.ts +165 -612
- package/extensions/lib/rollback.ts +0 -53
- package/extensions/lib/scheduling.ts +352 -450
- package/extensions/lib/selection.ts +212 -390
- package/extensions/lib/tool-surface.ts +29 -46
- package/extensions/lib/transcript.ts +47 -166
- package/extensions/settings.ts +481 -0
- package/package.json +4 -1
- package/extensions/lib/surfacing.ts +0 -604
- package/extensions/summarizer.js +0 -291
|
@@ -1,24 +1,5 @@
|
|
|
1
1
|
import { evidenceSha256, sha256Value } from "../json.ts";
|
|
2
2
|
import { ownValue } from "./canonical.ts";
|
|
3
|
-
import {
|
|
4
|
-
MAX_CONTEXT_ATTEMPT_RECORDS,
|
|
5
|
-
MAX_PROJECTION_HASH_RECORDS,
|
|
6
|
-
} from "./policy.ts";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Projection instrumentation.
|
|
10
|
-
*
|
|
11
|
-
* The iteration-1 ledger reported 19 "observed mutations", and reconstruction showed
|
|
12
|
-
* only 7 of them were projection rewrites this runtime performed. The other 12 were
|
|
13
|
-
* provider-side cache misses on projections that had only GROWN: message count up by
|
|
14
|
-
* one, no message removed, system prompt and tool digests constant, and the request
|
|
15
|
-
* still re-paid ~200k fresh tokens. One dial counting both makes a scheduling lever
|
|
16
|
-
* look responsible for a cost no scheduling lever can remove.
|
|
17
|
-
*
|
|
18
|
-
* So the two are counted separately, and the append case is made PROVABLE from
|
|
19
|
-
* artifacts: per-message digests of consecutive projections show exactly where, or
|
|
20
|
-
* whether, the prefix diverged.
|
|
21
|
-
*/
|
|
22
3
|
|
|
23
4
|
export type ProjectionChange = "identical" | "append" | "rewrite";
|
|
24
5
|
|
|
@@ -27,7 +8,6 @@ export interface ProjectionComparison {
|
|
|
27
8
|
previousCount: number;
|
|
28
9
|
nextCount: number;
|
|
29
10
|
appendedCount: number;
|
|
30
|
-
/** First index whose digest differs; null when the prefix is intact. */
|
|
31
11
|
firstDivergentIndex: number | null;
|
|
32
12
|
}
|
|
33
13
|
|
|
@@ -38,11 +18,6 @@ export function messageDigests(messages: readonly unknown[]): string[] {
|
|
|
38
18
|
});
|
|
39
19
|
}
|
|
40
20
|
|
|
41
|
-
/**
|
|
42
|
-
* Compare two consecutive projections by per-message digest. A pure append leaves the
|
|
43
|
-
* whole previous prefix intact, which is precisely the condition under which a
|
|
44
|
-
* provider cache SHOULD hit; anything else is a rewrite this runtime caused.
|
|
45
|
-
*/
|
|
46
21
|
export function compareProjections(
|
|
47
22
|
previous: readonly string[] | null,
|
|
48
23
|
next: readonly string[],
|
|
@@ -84,41 +59,15 @@ export interface ProjectionRecord extends ProjectionComparison {
|
|
|
84
59
|
prefixSha256: string;
|
|
85
60
|
}
|
|
86
61
|
|
|
87
|
-
/** One provider response read against the projection that produced it. */
|
|
88
62
|
export interface CacheObservation {
|
|
89
63
|
ordinal: number;
|
|
90
64
|
change: ProjectionChange;
|
|
91
65
|
inputTokens: number;
|
|
92
66
|
cacheReadTokens: number;
|
|
93
|
-
|
|
67
|
+
cacheWriteTokens: number;
|
|
94
68
|
providerSideMiss: boolean;
|
|
95
69
|
}
|
|
96
70
|
|
|
97
|
-
/**
|
|
98
|
-
* THE context-management event stream.
|
|
99
|
-
*
|
|
100
|
-
* One stream, one envelope, one naming convention. Every context event this runtime
|
|
101
|
-
* produces -- tool attempts and their refusals, span corrections, gate lifecycle,
|
|
102
|
-
* receipts, commits, absorptions, splits, recoveries, projections and provider usage --
|
|
103
|
-
* lands here as a flat JSONL-style record, and every record is also appended as a
|
|
104
|
-
* durable session entry so an analyst can reconstruct the whole timeline from session
|
|
105
|
-
* artifacts alone.
|
|
106
|
-
*
|
|
107
|
-
* The envelope is fixed and self-describing:
|
|
108
|
-
* v schema version of the envelope
|
|
109
|
-
* seq monotonic per session, from 1, so records order without a clock
|
|
110
|
-
* kind "context.<noun>", the only field a reader must know to dispatch
|
|
111
|
-
* session_id the session these events belong to
|
|
112
|
-
* ordinal transcript position, the deterministic clock this runtime reasons in
|
|
113
|
-
* at wall-clock milliseconds, the join key against provider-side telemetry
|
|
114
|
-
* revision durable state revision at emit time
|
|
115
|
-
*
|
|
116
|
-
* Everything after the envelope is a FLAT snake_case payload. Records are not shaped
|
|
117
|
-
* around any metric we compute today: a reader that wants a number we never thought of
|
|
118
|
-
* should be able to get it with a query rather than a code change. Nothing nests, and
|
|
119
|
-
* anything one-to-many (a correction, an absorbed wedge, a created fold) is its own
|
|
120
|
-
* record referencing the record it belongs to by `seq`.
|
|
121
|
-
*/
|
|
122
71
|
export const CONTEXT_EVENT_SCHEMA_VERSION = 1;
|
|
123
72
|
|
|
124
73
|
export interface ContextEvent {
|
|
@@ -133,63 +82,29 @@ export interface ContextEvent {
|
|
|
133
82
|
}
|
|
134
83
|
|
|
135
84
|
export type ContextEventKind =
|
|
136
|
-
/** One context-management tool call, accepted or refused. */
|
|
137
85
|
| "context.attempt"
|
|
138
|
-
/** One auto-snap applied to a requested span, referencing its attempt. */
|
|
139
86
|
| "context.correction"
|
|
140
|
-
|
|
141
|
-
| "context.protect"
|
|
142
|
-
/** The resolved serving budget, stated once at startup so a live run can prove it. */
|
|
87
|
+
| "context.pin"
|
|
143
88
|
| "context.capacity"
|
|
144
|
-
/** One receipt delivered into the window. */
|
|
145
89
|
| "context.receipt"
|
|
146
|
-
/** One post-commit rider composed and persisted: at most one per fold epoch. */
|
|
147
|
-
| "context.rider"
|
|
148
|
-
/** One pre-commit last-call exposure: the band-top prompt, with its telemetry. */
|
|
149
90
|
| "context.lastcall"
|
|
150
|
-
/** One gated round's outcome, attributed to the exposure it answers. */
|
|
151
91
|
| "context.response"
|
|
152
|
-
/** One surfacing suggestion issued: both channel scores, the divergence, the carrier. */
|
|
153
92
|
| "context.suggestion"
|
|
154
|
-
/** One surfacing suggestion graded: the acted/used/ignored join and its transition. */
|
|
155
93
|
| "context.outcome"
|
|
156
|
-
/** One append-once occupancy threshold notice delivered into the window. */
|
|
157
94
|
| "context.notice"
|
|
158
|
-
|
|
95
|
+
| "context.frontier"
|
|
159
96
|
| "context.commit"
|
|
160
|
-
/** One fold created, by the ladder or by the agent. */
|
|
161
97
|
| "context.fold"
|
|
162
|
-
/**
|
|
163
|
-
* One generator call for a brief: what it read, what it wrote, and what it cost.
|
|
164
|
-
*
|
|
165
|
-
* The generator runs outside the session, so its spend appears in no provider ledger
|
|
166
|
-
* and its latency appears in no turn. Without this record a deployment cannot say what
|
|
167
|
-
* briefing costs it, which is the question the async lane exists to answer.
|
|
168
|
-
*/
|
|
169
98
|
| "context.brief"
|
|
170
|
-
/** One boundary sliver absorbed into its later neighbour. */
|
|
171
99
|
| "context.absorb"
|
|
172
|
-
/** One oversized span split into sequential bite-sized folds. */
|
|
173
100
|
| "context.split"
|
|
174
|
-
/** One overflow rollback and the recovery that followed it. */
|
|
175
101
|
| "context.recovery"
|
|
176
|
-
|
|
102
|
+
| "context.suspend"
|
|
177
103
|
| "context.rollback"
|
|
178
|
-
/** One projection built, classified against the one before it. */
|
|
179
104
|
| "context.projection"
|
|
180
|
-
/** One provider response, which is where this stream joins provider usage. */
|
|
181
105
|
| "context.usage"
|
|
182
|
-
/** One handoff, compared byte-for-byte against the previous transmitted projection. */
|
|
183
106
|
| "context.prefix";
|
|
184
107
|
|
|
185
|
-
/**
|
|
186
|
-
* Where the prefix first differs from the previous TRANSMITTED projection, in chars.
|
|
187
|
-
*
|
|
188
|
-
* A positional cache is keyed on a byte prefix, so the message-level classification is
|
|
189
|
-
* not enough to attribute a miss: the analyst needs the position, and only the runtime
|
|
190
|
-
* knows why a byte at that position moved. This is the cheap half -- both projections
|
|
191
|
-
* are in hand at handoff -- and it is emission only. No analysis of it belongs here.
|
|
192
|
-
*/
|
|
193
108
|
export function prefixDivergence(
|
|
194
109
|
previous: string | null,
|
|
195
110
|
next: string,
|
|
@@ -208,18 +123,14 @@ export function prefixDivergence(
|
|
|
208
123
|
|
|
209
124
|
export interface InstrumentationLedger {
|
|
210
125
|
projections: number;
|
|
211
|
-
/** Projections this runtime actually rewrote. The scheduling dial. */
|
|
212
126
|
rewrites: number;
|
|
213
127
|
appends: number;
|
|
214
128
|
identical: number;
|
|
215
|
-
/** Responses that re-read an unchanged prefix from scratch. The provider's dial. */
|
|
216
129
|
providerSideMisses: number;
|
|
217
130
|
observedMisses: number;
|
|
218
131
|
records: ProjectionRecord[];
|
|
219
132
|
observations: CacheObservation[];
|
|
220
|
-
/** The stream itself, oldest first. Bounded in memory; durable in full. */
|
|
221
133
|
events: ContextEvent[];
|
|
222
|
-
/** The monotonic sequence every record carries. */
|
|
223
134
|
sequence: number;
|
|
224
135
|
countsByKind: Record<string, number>;
|
|
225
136
|
}
|
|
@@ -240,10 +151,6 @@ export function emptyLedger(): InstrumentationLedger {
|
|
|
240
151
|
};
|
|
241
152
|
}
|
|
242
153
|
|
|
243
|
-
/**
|
|
244
|
-
* Stamp the envelope and append. The in-memory ring is the status view and is bounded;
|
|
245
|
-
* the durable session entry is the audit copy and is not.
|
|
246
|
-
*/
|
|
247
154
|
export function recordContextEvent(
|
|
248
155
|
ledger: InstrumentationLedger,
|
|
249
156
|
kind: ContextEventKind,
|
|
@@ -263,19 +170,9 @@ export function recordContextEvent(
|
|
|
263
170
|
};
|
|
264
171
|
ledger.countsByKind[kind] = (ledger.countsByKind[kind] ?? 0) + 1;
|
|
265
172
|
ledger.events.push(record);
|
|
266
|
-
if (ledger.events.length > MAX_CONTEXT_ATTEMPT_RECORDS) {
|
|
267
|
-
ledger.events = ledger.events.slice(ledger.events.length - MAX_CONTEXT_ATTEMPT_RECORDS);
|
|
268
|
-
}
|
|
269
173
|
return record;
|
|
270
174
|
}
|
|
271
175
|
|
|
272
|
-
function bounded<T>(values: T[], next: T): T[] {
|
|
273
|
-
values.push(next);
|
|
274
|
-
return values.length > MAX_PROJECTION_HASH_RECORDS
|
|
275
|
-
? values.slice(values.length - MAX_PROJECTION_HASH_RECORDS)
|
|
276
|
-
: values;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
176
|
export function recordProjection(
|
|
280
177
|
ledger: InstrumentationLedger,
|
|
281
178
|
comparison: ProjectionComparison,
|
|
@@ -285,29 +182,13 @@ export function recordProjection(
|
|
|
285
182
|
if (comparison.change === "rewrite") ledger.rewrites += 1;
|
|
286
183
|
else if (comparison.change === "append") ledger.appends += 1;
|
|
287
184
|
else ledger.identical += 1;
|
|
288
|
-
ledger.records
|
|
185
|
+
ledger.records.push({
|
|
289
186
|
...comparison,
|
|
290
187
|
ordinal: ledger.projections,
|
|
291
|
-
// The prefix digest, not the whole projection: it is what a positional cache keys
|
|
292
|
-
// on, so two equal values are the claim that a cache hit was available.
|
|
293
188
|
prefixSha256: sha256Value(digests.slice(0, comparison.previousCount || digests.length)),
|
|
294
189
|
});
|
|
295
190
|
}
|
|
296
191
|
|
|
297
|
-
/**
|
|
298
|
-
* Read one provider response against the projection that produced it. A miss on a
|
|
299
|
-
* projection that only grew is provider-side by construction: no byte we control
|
|
300
|
-
* moved, so no scheduling change could have prevented the re-read.
|
|
301
|
-
*
|
|
302
|
-
* A rewrite used to be charged to us unconditionally, and that hid the whole rep 21
|
|
303
|
-
* finding: a rewrite that preserved 99.86% of the prompt still read back as ours, so
|
|
304
|
-
* our own metrics reported a negligible divergence and a total cache loss and never
|
|
305
|
-
* put the two side by side. Attribution now asks how much of the previous prompt the
|
|
306
|
-
* rewrite actually kept. If we preserved nearly all of it and the provider still
|
|
307
|
-
* served nothing, our divergence cannot account for the loss and the miss is theirs.
|
|
308
|
-
* That is a claim about ATTRIBUTION, not about cost: a preserved-share miss is still
|
|
309
|
-
* a full rebuild, and it is still worth not causing.
|
|
310
|
-
*/
|
|
311
192
|
export const PROVIDER_SIDE_MISS_PRESERVED_SHARE = 0.99;
|
|
312
193
|
|
|
313
194
|
export function observeCacheUsage(
|
|
@@ -317,9 +198,14 @@ export function observeCacheUsage(
|
|
|
317
198
|
const usage = input.usage;
|
|
318
199
|
const inputTokens = ownValue(usage, "input");
|
|
319
200
|
const cacheReadTokens = ownValue(usage, "cacheRead");
|
|
201
|
+
const rawCacheWriteTokens = ownValue(usage, "cacheWrite");
|
|
320
202
|
if (typeof inputTokens !== "number" || !Number.isFinite(inputTokens) || inputTokens <= 0 ||
|
|
321
203
|
typeof cacheReadTokens !== "number" || !Number.isFinite(cacheReadTokens) ||
|
|
322
204
|
cacheReadTokens < 0) return null;
|
|
205
|
+
const cacheWriteTokens = typeof rawCacheWriteTokens === "number" &&
|
|
206
|
+
Number.isFinite(rawCacheWriteTokens) && rawCacheWriteTokens >= 0
|
|
207
|
+
? rawCacheWriteTokens
|
|
208
|
+
: 0;
|
|
323
209
|
const missed = cacheReadTokens === 0;
|
|
324
210
|
const preserved = typeof input.preservedShare === "number" && Number.isFinite(input.preservedShare)
|
|
325
211
|
? input.preservedShare
|
|
@@ -329,16 +215,16 @@ export function observeCacheUsage(
|
|
|
329
215
|
change: input.change,
|
|
330
216
|
inputTokens,
|
|
331
217
|
cacheReadTokens,
|
|
332
|
-
|
|
218
|
+
cacheWriteTokens,
|
|
219
|
+
providerSideMiss: missed && cacheWriteTokens === 0 && (input.change !== "rewrite" ||
|
|
333
220
|
(preserved !== null && preserved >= PROVIDER_SIDE_MISS_PRESERVED_SHARE)),
|
|
334
221
|
};
|
|
335
222
|
if (missed) ledger.observedMisses += 1;
|
|
336
223
|
if (observation.providerSideMiss) ledger.providerSideMisses += 1;
|
|
337
|
-
ledger.observations
|
|
224
|
+
ledger.observations.push(observation);
|
|
338
225
|
return observation;
|
|
339
226
|
}
|
|
340
227
|
|
|
341
|
-
/** The compact ledger the evidence envelope carries; the record rings stay for audit. */
|
|
342
228
|
export function ledgerSummary(ledger: InstrumentationLedger): Record<string, unknown> {
|
|
343
229
|
return {
|
|
344
230
|
projections: ledger.projections,
|
|
@@ -89,32 +89,12 @@ export function contextUsageRatio(value: unknown): number | null {
|
|
|
89
89
|
return tokens / contextWindow;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
/**
|
|
93
|
-
* A measured WINDOW ratio, restated as occupancy of the serving budget.
|
|
94
|
-
*
|
|
95
|
-
* The provider reports tokens against its total window and the thermostat is declared
|
|
96
|
-
* against the serving budget, so exactly one conversion stands between them and it is
|
|
97
|
-
* written here rather than inferred at each reader. Before this build the trigger
|
|
98
|
-
* divided by the budget while the freeing target and the backstop divided by the
|
|
99
|
-
* window, which at a 383,616-token budget behind a 400,000-token window is a 4.5-point
|
|
100
|
-
* disagreement, and 10 points at a 100,000-token fixture.
|
|
101
|
-
*/
|
|
102
92
|
export function budgetOccupancy(snapshot: ActiveContextSnapshot, windowRatio: number | null): number | null {
|
|
103
93
|
if (typeof windowRatio !== "number" || !Number.isFinite(windowRatio)) return null;
|
|
104
94
|
if (!(snapshot.budgetTokens > 0) || !(snapshot.contextWindow > 0)) return null;
|
|
105
95
|
return windowRatio * snapshot.contextWindow / snapshot.budgetTokens;
|
|
106
96
|
}
|
|
107
97
|
|
|
108
|
-
/**
|
|
109
|
-
* The fence, as a ratio of the window the measured ratio is denominated against.
|
|
110
|
-
*
|
|
111
|
-
* A measured ratio is tokens over `contextWindow`, so "the request reached its serving
|
|
112
|
-
* budget" is exactly `ratio >= budgetTokens / contextWindow`. When the snapshot carries
|
|
113
|
-
* a budget the runtime uses it verbatim: a deployment that DECLARED its input budget has
|
|
114
|
-
* already netted out its output reservation, and re-subtracting one here would fence it
|
|
115
|
-
* roughly four points early. Only the ctx fallback, where nothing but a raw provider
|
|
116
|
-
* window is known, still estimates the reservation.
|
|
117
|
-
*/
|
|
118
98
|
export function hardFenceRatio(value?: unknown, ctx?: any): number {
|
|
119
99
|
const direct = ownValue(value, "contextWindow");
|
|
120
100
|
const reported = ownValue(ctx?.getContextUsage?.(), "contextWindow");
|
|
@@ -134,45 +114,16 @@ export function hardFenceRatio(value?: unknown, ctx?: any): number {
|
|
|
134
114
|
return (contextWindow - reserve) / contextWindow;
|
|
135
115
|
}
|
|
136
116
|
|
|
137
|
-
/**
|
|
138
|
-
* What the runtime is actually allowed to send, in tokens.
|
|
139
|
-
*
|
|
140
|
-
* The per-request max-input DESCRIPTOR a provider advertises assumes a full output
|
|
141
|
-
* reservation, so it understates the real ceiling by whatever the deployment did not
|
|
142
|
-
* reserve. Measured 2026-08-06 on openai-codex gpt-5.x: a run aborted at ~297k
|
|
143
|
-
* projected tokens against a 272k descriptor, in the same session where the provider
|
|
144
|
-
* had just accepted 339,689 tokens. Serving admits roughly the TOTAL window minus the
|
|
145
|
-
* output reservation actually in force, which at a 16,384 reservation is ~384k.
|
|
146
|
-
*
|
|
147
|
-
* Every number here is introspectable in status on purpose: a fence nobody can audit
|
|
148
|
-
* is how a 25 percent error survived four instrumented runs.
|
|
149
|
-
*/
|
|
150
117
|
export interface CapacityAccounting {
|
|
151
|
-
/** "truthful" once the deployment declares its own serving budget. */
|
|
152
118
|
mode: "descriptor" | "truthful";
|
|
153
|
-
/** The denominator every pressure ratio and fence is computed against. */
|
|
154
119
|
window: number;
|
|
155
120
|
outputReservation: number;
|
|
156
|
-
/** The token ceiling: window minus the output reservation. */
|
|
157
121
|
budgetTokens: number;
|
|
158
122
|
usedTokens: number | null;
|
|
159
123
|
headroomTokens: number | null;
|
|
160
|
-
/** What the provider descriptor claimed, kept so the gap stays visible. */
|
|
161
124
|
descriptorWindow: number | null;
|
|
162
125
|
}
|
|
163
126
|
|
|
164
|
-
/**
|
|
165
|
-
* ONE denominator, and no guess in front of it.
|
|
166
|
-
*
|
|
167
|
-
* `truthful` means the deployment stated the budget it may actually fill, already net of
|
|
168
|
-
* whatever output reservation it holds back, so the value passes through untouched and
|
|
169
|
-
* the reservation this runtime reports is zero because it withheld nothing. Only the
|
|
170
|
-
* descriptor fallback still subtracts, because a per-request max-input descriptor is a
|
|
171
|
-
* total-window claim and the reservation inside it has to be estimated. That estimate was
|
|
172
|
-
* the defect the reshape removed: the runtime subtracted a fixed 16,384 tokens capped at
|
|
173
|
-
* a tenth of the window while the documentation claimed it subtracted the reservation
|
|
174
|
-
* actually in force.
|
|
175
|
-
*/
|
|
176
127
|
export function capacityAccounting(input: {
|
|
177
128
|
window: number;
|
|
178
129
|
truthful: boolean;
|
|
@@ -352,8 +303,6 @@ export function persistenceProjection(state: ActiveContextState, snapshot: Activ
|
|
|
352
303
|
leases: Object.fromEntries(Object.entries(state.leases)
|
|
353
304
|
.filter(([id]) => retained.has(id))),
|
|
354
305
|
};
|
|
355
|
-
// A pending mark is a promise about evidence that is still in the branch. Once its
|
|
356
|
-
// source or its named fold leaves, the mark is unfulfillable and must not survive.
|
|
357
306
|
const survivingMarks = (state.pendingMarks ?? []).filter((mark) => mark.mark === "refold"
|
|
358
307
|
? retained.has(mark.id)
|
|
359
308
|
: mark.parts.every((part) => part.kind === "raw"
|
|
@@ -362,8 +311,6 @@ export function persistenceProjection(state: ActiveContextState, snapshot: Activ
|
|
|
362
311
|
if (survivingMarks.length) projected.pendingMarks = clone(survivingMarks);
|
|
363
312
|
else delete projected.pendingMarks;
|
|
364
313
|
delete projected.pinnedPeeks;
|
|
365
|
-
// A brief correction names a fold; once that fold is gone the correction has
|
|
366
|
-
// nothing to present, exactly like a mark whose source left the branch.
|
|
367
314
|
const survivingBriefs = Object.entries(state.briefs ?? {}).filter(([id]) => retained.has(id));
|
|
368
315
|
if (survivingBriefs.length) projected.briefs = Object.fromEntries(clone(survivingBriefs));
|
|
369
316
|
else delete projected.briefs;
|
|
@@ -378,6 +325,12 @@ export function explicitProtectedKeys(state: ActiveContextState): Set<string> {
|
|
|
378
325
|
return new Set(state.protected.map(objectRefKey));
|
|
379
326
|
}
|
|
380
327
|
|
|
328
|
+
/**
|
|
329
|
+
* Held against folding: pinned by name, or unfoldable for validity (an unfinished turn, an
|
|
330
|
+
* entry with no evidence ref). There used to be a second reading of this for tool spans,
|
|
331
|
+
* differing only in which protected set it consulted; the two sets collapsed into one when
|
|
332
|
+
* fresh-tail was deleted, and the two functions were byte-identical after that.
|
|
333
|
+
*/
|
|
381
334
|
export function refsProtected(
|
|
382
335
|
refs: EvidenceRef[],
|
|
383
336
|
state: ActiveContextState,
|
|
@@ -390,23 +343,6 @@ export function refsProtected(
|
|
|
390
343
|
});
|
|
391
344
|
}
|
|
392
345
|
|
|
393
|
-
export function toolRefsProtected(
|
|
394
|
-
refs: EvidenceRef[],
|
|
395
|
-
state: ActiveContextState,
|
|
396
|
-
snapshot: ActiveContextSnapshot,
|
|
397
|
-
): boolean {
|
|
398
|
-
const explicit = explicitProtectedKeys(state);
|
|
399
|
-
return refs.some((ref) => {
|
|
400
|
-
const item = exactMapped(snapshot, ref);
|
|
401
|
-
return !item || explicit.has(objectRefKey(ref)) || snapshot.toolProtectedIndices.has(item.index);
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
/**
|
|
406
|
-
* Mass the agent's pins hold out of the ladder's reach: explicitly protected evidence
|
|
407
|
-
* outside the fresh tail. A pin's cost must be a number the agent sees on receipts,
|
|
408
|
-
* never a silent commit shortfall.
|
|
409
|
-
*/
|
|
410
346
|
export function protectedStaleMass(
|
|
411
347
|
snapshot: ActiveContextSnapshot,
|
|
412
348
|
state: ActiveContextState,
|
|
@@ -424,12 +360,6 @@ export function protectedStaleMass(
|
|
|
424
360
|
return { bytes: total, refs };
|
|
425
361
|
}
|
|
426
362
|
|
|
427
|
-
/**
|
|
428
|
-
* Every byte protect currently holds raw, fresh or stale: the mass the pinned-share
|
|
429
|
-
* cap is measured against. protectedStaleMass narrows to what a commit could not
|
|
430
|
-
* reclaim TODAY; the cap has to see the whole promise, because a pin inside the
|
|
431
|
-
* fresh tail outlives the tail.
|
|
432
|
-
*/
|
|
433
363
|
export function explicitProtectedMass(
|
|
434
364
|
snapshot: ActiveContextSnapshot,
|
|
435
365
|
state: ActiveContextState,
|
|
@@ -452,16 +382,9 @@ export interface AdmissionVerdict {
|
|
|
452
382
|
requestedTokens: number;
|
|
453
383
|
headroomTokens: number | null;
|
|
454
384
|
budgetTokens: number;
|
|
455
|
-
/** Bytes of the target that WOULD fit; 0 when even the minimum slice will not. */
|
|
456
385
|
affordableBytes: number;
|
|
457
386
|
}
|
|
458
387
|
|
|
459
|
-
/**
|
|
460
|
-
* Preflight one read against the exact stored size of its target and the truthful
|
|
461
|
-
* headroom. A read that has not been measured yet is ADMITTED and says so: refusing
|
|
462
|
-
* on absent data would stall a session for a number we never had, and the absence is
|
|
463
|
-
* reported rather than papered over.
|
|
464
|
-
*/
|
|
465
388
|
export function admissionVerdict(input: {
|
|
466
389
|
requestedBytes: number;
|
|
467
390
|
capacity: CapacityAccounting;
|