@principles/core 1.241.0 → 1.243.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/dist/runtime-v2/feature-flags/__tests__/context-manifest-budget-flag.test.d.ts +12 -0
- package/dist/runtime-v2/feature-flags/__tests__/context-manifest-budget-flag.test.d.ts.map +1 -0
- package/dist/runtime-v2/feature-flags/__tests__/context-manifest-budget-flag.test.js +114 -0
- package/dist/runtime-v2/feature-flags/__tests__/context-manifest-budget-flag.test.js.map +1 -0
- package/dist/runtime-v2/feature-flags/__tests__/progressive-evaluator-flag.test.d.ts +8 -0
- package/dist/runtime-v2/feature-flags/__tests__/progressive-evaluator-flag.test.d.ts.map +1 -0
- package/dist/runtime-v2/feature-flags/__tests__/progressive-evaluator-flag.test.js +56 -0
- package/dist/runtime-v2/feature-flags/__tests__/progressive-evaluator-flag.test.js.map +1 -0
- package/dist/runtime-v2/feature-flags/feature-flag-contract.d.ts.map +1 -1
- package/dist/runtime-v2/feature-flags/feature-flag-contract.js +14 -0
- package/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
- package/dist/runtime-v2/internalization/__tests__/candidate-lineage.property.test.d.ts +15 -0
- package/dist/runtime-v2/internalization/__tests__/candidate-lineage.property.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/candidate-lineage.property.test.js +301 -0
- package/dist/runtime-v2/internalization/__tests__/candidate-lineage.property.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/context-manifest.property.test.d.ts +12 -0
- package/dist/runtime-v2/internalization/__tests__/context-manifest.property.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/context-manifest.property.test.js +176 -0
- package/dist/runtime-v2/internalization/__tests__/context-manifest.property.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-evaluator.property.test.d.ts +11 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-evaluator.property.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-evaluator.property.test.js +154 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-evaluator.property.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/prompt-budget-manager.property.test.d.ts +12 -0
- package/dist/runtime-v2/internalization/__tests__/prompt-budget-manager.property.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/prompt-budget-manager.property.test.js +254 -0
- package/dist/runtime-v2/internalization/__tests__/prompt-budget-manager.property.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/resolve-injection.property.test.d.ts +15 -0
- package/dist/runtime-v2/internalization/__tests__/resolve-injection.property.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/resolve-injection.property.test.js +150 -0
- package/dist/runtime-v2/internalization/__tests__/resolve-injection.property.test.js.map +1 -0
- package/dist/runtime-v2/internalization/artificer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/artificer-runner.js +12 -0
- package/dist/runtime-v2/internalization/artificer-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/candidate-lineage.d.ts +136 -0
- package/dist/runtime-v2/internalization/candidate-lineage.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/candidate-lineage.js +230 -0
- package/dist/runtime-v2/internalization/candidate-lineage.js.map +1 -0
- package/dist/runtime-v2/internalization/context-manifest.d.ts +117 -0
- package/dist/runtime-v2/internalization/context-manifest.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/context-manifest.js +156 -0
- package/dist/runtime-v2/internalization/context-manifest.js.map +1 -0
- package/dist/runtime-v2/internalization/context-manifests.d.ts +74 -0
- package/dist/runtime-v2/internalization/context-manifests.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/context-manifests.js +192 -0
- package/dist/runtime-v2/internalization/context-manifests.js.map +1 -0
- package/dist/runtime-v2/internalization/dimension-coverage-policy.d.ts +54 -0
- package/dist/runtime-v2/internalization/dimension-coverage-policy.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/dimension-coverage-policy.js +70 -0
- package/dist/runtime-v2/internalization/dimension-coverage-policy.js.map +1 -0
- package/dist/runtime-v2/internalization/dreamer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/dreamer-runner.js +13 -1
- package/dist/runtime-v2/internalization/dreamer-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/evaluator-output.d.ts +26 -0
- package/dist/runtime-v2/internalization/evaluator-output.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/evaluator-output.js +7 -1
- package/dist/runtime-v2/internalization/evaluator-output.js.map +1 -1
- package/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/evaluator-runner.js +13 -0
- package/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/progressive-evaluator.d.ts +115 -0
- package/dist/runtime-v2/internalization/progressive-evaluator.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/progressive-evaluator.js +248 -0
- package/dist/runtime-v2/internalization/progressive-evaluator.js.map +1 -0
- package/dist/runtime-v2/internalization/prompt-budget-manager.d.ts +90 -0
- package/dist/runtime-v2/internalization/prompt-budget-manager.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/prompt-budget-manager.js +149 -0
- package/dist/runtime-v2/internalization/prompt-budget-manager.js.map +1 -0
- package/dist/runtime-v2/internalization/resolve-injection.d.ts +62 -0
- package/dist/runtime-v2/internalization/resolve-injection.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/resolve-injection.js +78 -0
- package/dist/runtime-v2/internalization/resolve-injection.js.map +1 -0
- package/dist/runtime-v2/internalization/scribe-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/scribe-runner.js +10 -0
- package/dist/runtime-v2/internalization/scribe-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/summary-field-reader.d.ts +38 -0
- package/dist/runtime-v2/internalization/summary-field-reader.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/summary-field-reader.js +108 -0
- package/dist/runtime-v2/internalization/summary-field-reader.js.map +1 -0
- package/dist/runtime-v2/runner/base-peer-runner.d.ts +36 -0
- package/dist/runtime-v2/runner/base-peer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/runner/base-peer-runner.js +71 -0
- package/dist/runtime-v2/runner/base-peer-runner.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 2 — Progressive Evaluator: two-stage evaluation with flagged criteria
|
|
3
|
+
* (design §6.5).
|
|
4
|
+
*
|
|
5
|
+
* Pure logic only (Core vs Plugin boundary, `antipattern-core-io`).
|
|
6
|
+
*
|
|
7
|
+
* The progressive evaluator runs Stage 1 (summary-level context) first; only
|
|
8
|
+
* when flagged / undetermined / forced-sample does it trigger Stage 2 (tier2
|
|
9
|
+
* full-contentJson re-evaluation). This keeps daily cost low while enabling
|
|
10
|
+
* deep diagnosis on demand.
|
|
11
|
+
*
|
|
12
|
+
* Key invariants:
|
|
13
|
+
* - Stage 2 output is INDEPENDENT — never merged with Stage 1 (rc-7 /
|
|
14
|
+
* ERR-015 / ERR-018 / ERR-019). `stagesRun === 2` ⟹ `finalOutput` is
|
|
15
|
+
* entirely the Stage 2 result.
|
|
16
|
+
* - flagged criteria read DIMENSION_COVERAGE_POLICY (single source of truth),
|
|
17
|
+
* never inline a dimension-name array. Untrusted LLM input is filtered by
|
|
18
|
+
* the policy table before flagging (rc-1 / rc-4).
|
|
19
|
+
* - Deterministic forced sampling via fnv1a32 (no Math.random, design §4.4).
|
|
20
|
+
*/
|
|
21
|
+
import { isRequiredDimension } from './dimension-coverage-policy.js';
|
|
22
|
+
// ── Constants ────────────────────────────────────────────────────────────────
|
|
23
|
+
export const IMPLEMENTATION_FIDELITY_THRESHOLD = 0.7;
|
|
24
|
+
export const FORCED_STAGE2_SAMPLE_MODULUS = 20; // ~5%
|
|
25
|
+
// ── Runtime guards (rc-1 / rc-2 / rc-5) ──────────────────────────────────────
|
|
26
|
+
function isRecord(value) {
|
|
27
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
28
|
+
}
|
|
29
|
+
function readNumber(source, key) {
|
|
30
|
+
if (!isRecord(source) || !Object.hasOwn(source, key))
|
|
31
|
+
return null;
|
|
32
|
+
const v = source[key];
|
|
33
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : null;
|
|
34
|
+
}
|
|
35
|
+
function readBoolean(source, key) {
|
|
36
|
+
if (!isRecord(source) || !Object.hasOwn(source, key))
|
|
37
|
+
return null;
|
|
38
|
+
const v = source[key];
|
|
39
|
+
return typeof v === 'boolean' ? v : null;
|
|
40
|
+
}
|
|
41
|
+
function readStringArray(source, key) {
|
|
42
|
+
if (!isRecord(source) || !Object.hasOwn(source, key))
|
|
43
|
+
return null;
|
|
44
|
+
const v = source[key];
|
|
45
|
+
if (!Array.isArray(v))
|
|
46
|
+
return null;
|
|
47
|
+
return v.filter((el) => typeof el === 'string');
|
|
48
|
+
}
|
|
49
|
+
// ── Deterministic forced sampling (design §4.4) ───────────────────────────────
|
|
50
|
+
/**
|
|
51
|
+
* FNV-1a 32-bit hash. Pure, deterministic, no crypto, no Math.random.
|
|
52
|
+
* Supports the full Unicode range in the input string.
|
|
53
|
+
*/
|
|
54
|
+
export function fnv1a32(input) {
|
|
55
|
+
let hash = 0x811c9dc5;
|
|
56
|
+
for (let i = 0; i < input.length; i++) {
|
|
57
|
+
hash ^= input.charCodeAt(i);
|
|
58
|
+
// FNV prime multiplication with 32-bit overflow wrapping.
|
|
59
|
+
hash = Math.imul(hash, 0x01000193);
|
|
60
|
+
}
|
|
61
|
+
// Convert to unsigned 32-bit.
|
|
62
|
+
return hash >>> 0;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Deterministic ~5% forced Stage 2 sampling. `fnv1a32(taskId) % modulus === 0`.
|
|
66
|
+
* Never uses Math.random or any non-deterministic source (design §4.4).
|
|
67
|
+
*/
|
|
68
|
+
export function isForcedStage2(taskId, modulus = FORCED_STAGE2_SAMPLE_MODULUS) {
|
|
69
|
+
if (modulus <= 0)
|
|
70
|
+
return false;
|
|
71
|
+
return fnv1a32(taskId) % modulus === 0;
|
|
72
|
+
}
|
|
73
|
+
// ── Flagged criteria (design §6.5.3) ─────────────────────────────────────────
|
|
74
|
+
/**
|
|
75
|
+
* Evaluate the three flagged criteria against Stage 1 output (design §6.5.3).
|
|
76
|
+
*
|
|
77
|
+
* Preconditions: none — `stage1Output` is untrusted LLM output (rc-1), read
|
|
78
|
+
* via `Object.hasOwn` (rc-5 / ERR-013).
|
|
79
|
+
*
|
|
80
|
+
* Postconditions:
|
|
81
|
+
* - `flagged === true` iff `reasons` is non-empty.
|
|
82
|
+
* - Three criteria: required-dimension missing, painCoverage.fullyCovered === false,
|
|
83
|
+
* implementationFidelity.score < 0.7.
|
|
84
|
+
* - Fields missing or wrong type → `undetermined` (rc-3: fail loud, never
|
|
85
|
+
* silently pass).
|
|
86
|
+
*
|
|
87
|
+
* Dimension criterion (§6.5.3, measurement-driven tightening):
|
|
88
|
+
* - Only **required** dimension names in `missingDimensions` count. The LLM
|
|
89
|
+
* may stuff optional/excluded/unknown strings in — all filtered out by
|
|
90
|
+
* `isRequiredDimension` before checking. `optionalUncovered` never enters
|
|
91
|
+
* `reasons`.
|
|
92
|
+
*/
|
|
93
|
+
export function evaluateFlaggedCriteria(stage1Output) {
|
|
94
|
+
const reasons = [];
|
|
95
|
+
const undetermined = [];
|
|
96
|
+
if (!isRecord(stage1Output)) {
|
|
97
|
+
// Entire output is malformed — all criteria undetermined.
|
|
98
|
+
return { flagged: false, reasons: [], undetermined: ['output_not_object'] };
|
|
99
|
+
}
|
|
100
|
+
// Criterion 1: required dimension missing (filtered, §6.5.3).
|
|
101
|
+
const compressionFidelity = Object.hasOwn(stage1Output, 'compressionFidelity')
|
|
102
|
+
? stage1Output.compressionFidelity
|
|
103
|
+
: undefined;
|
|
104
|
+
if (isRecord(compressionFidelity)) {
|
|
105
|
+
const missingDims = readStringArray(compressionFidelity, 'missingDimensions');
|
|
106
|
+
if (missingDims === null) {
|
|
107
|
+
undetermined.push('compressionFidelity.missingDimensions');
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
// rc-4: filter untrusted input through the policy table.
|
|
111
|
+
const requiredMissing = missingDims.filter((d) => isRequiredDimension(d));
|
|
112
|
+
if (requiredMissing.length > 0) {
|
|
113
|
+
reasons.push('missing_dimensions');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
undetermined.push('compressionFidelity');
|
|
119
|
+
}
|
|
120
|
+
// Criterion 2: painCoverage.fullyCovered === false.
|
|
121
|
+
const painCoverage = Object.hasOwn(stage1Output, 'painCoverage')
|
|
122
|
+
? stage1Output.painCoverage
|
|
123
|
+
: undefined;
|
|
124
|
+
if (isRecord(painCoverage)) {
|
|
125
|
+
const fullyCovered = readBoolean(painCoverage, 'fullyCovered');
|
|
126
|
+
if (fullyCovered === null) {
|
|
127
|
+
undetermined.push('painCoverage.fullyCovered');
|
|
128
|
+
}
|
|
129
|
+
else if (fullyCovered === false) {
|
|
130
|
+
reasons.push('pain_not_fully_covered');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
undetermined.push('painCoverage');
|
|
135
|
+
}
|
|
136
|
+
// Criterion 3: implementationFidelity.score < threshold.
|
|
137
|
+
const implFidelity = Object.hasOwn(stage1Output, 'implementationFidelity')
|
|
138
|
+
? stage1Output.implementationFidelity
|
|
139
|
+
: undefined;
|
|
140
|
+
if (isRecord(implFidelity)) {
|
|
141
|
+
const score = readNumber(implFidelity, 'score');
|
|
142
|
+
if (score === null) {
|
|
143
|
+
undetermined.push('implementationFidelity.score');
|
|
144
|
+
}
|
|
145
|
+
else if (score < IMPLEMENTATION_FIDELITY_THRESHOLD) {
|
|
146
|
+
reasons.push('implementation_fidelity_below_threshold');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
undetermined.push('implementationFidelity');
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
flagged: reasons.length > 0,
|
|
154
|
+
reasons,
|
|
155
|
+
undetermined,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Run the two-stage progressive evaluation (design §6.5).
|
|
160
|
+
*
|
|
161
|
+
* Stage 1: evaluate with summary-level context → flagged criteria.
|
|
162
|
+
* If not flagged AND not forced AND no undetermined → return Stage 1 result.
|
|
163
|
+
* Otherwise → Stage 2: resolve tier2 lineage, evaluate independently.
|
|
164
|
+
*
|
|
165
|
+
* Stage 2 output is INDEPENDENT (rc-7 / ERR-015 / ERR-018 / ERR-019):
|
|
166
|
+
* - Stage 1 output is NOT injected into Stage 2 context.
|
|
167
|
+
* - Stage 2 concerns are NOT merged with Stage 1.
|
|
168
|
+
* - `stagesRun === 2` → `finalOutput` is entirely Stage 2's result.
|
|
169
|
+
*
|
|
170
|
+
* Forced sampling (~5%) serves as a false-negative check: if Stage 1 passed
|
|
171
|
+
* but Stage 2 finds new concerns, emit `stage1_false_negative`.
|
|
172
|
+
*/
|
|
173
|
+
/**
|
|
174
|
+
* Extract concern identifiers from an evaluator output (best-effort).
|
|
175
|
+
* The evaluator schema (evaluator-output.ts) defines `evaluation.concerns` as
|
|
176
|
+
* a `string[]` — each concern is a plain string. This function returns those
|
|
177
|
+
* strings directly, filtering out any non-string elements defensively.
|
|
178
|
+
* (CodeRabbit PR #1277: previously assumed concerns were objects with key/
|
|
179
|
+
* description fields, but the schema is string[] — the mismatch meant
|
|
180
|
+
* diffConcernKeys always returned empty, disabling false-negative detection.)
|
|
181
|
+
*/
|
|
182
|
+
function extractConcernKeys(output) {
|
|
183
|
+
if (!isRecord(output))
|
|
184
|
+
return [];
|
|
185
|
+
const evaluation = Object.hasOwn(output, 'evaluation') ? output.evaluation : undefined;
|
|
186
|
+
if (!isRecord(evaluation))
|
|
187
|
+
return [];
|
|
188
|
+
const concerns = Object.hasOwn(evaluation, 'concerns') ? evaluation.concerns : undefined;
|
|
189
|
+
if (!Array.isArray(concerns))
|
|
190
|
+
return [];
|
|
191
|
+
return concerns.filter((c) => typeof c === 'string' && c !== '');
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Compute the set difference of concern keys between Stage 2 and Stage 1.
|
|
195
|
+
* Returns keys present in Stage 2 but not in Stage 1.
|
|
196
|
+
*/
|
|
197
|
+
function diffConcernKeys(stage2Output, stage1Output) {
|
|
198
|
+
const keys1 = extractConcernKeys(stage1Output);
|
|
199
|
+
const keys2 = extractConcernKeys(stage2Output);
|
|
200
|
+
const set1 = new Set(keys1);
|
|
201
|
+
return keys2.filter((k) => !set1.has(k));
|
|
202
|
+
}
|
|
203
|
+
export async function runProgressiveEvaluation(deps) {
|
|
204
|
+
const emit = deps.emit ?? (() => undefined);
|
|
205
|
+
// Stage 1: summary-level evaluation.
|
|
206
|
+
const out1 = await deps.llm.evaluate(deps.stage1Context);
|
|
207
|
+
const d1 = evaluateFlaggedCriteria(out1);
|
|
208
|
+
const forced = isForcedStage2(deps.taskId);
|
|
209
|
+
// No Stage 2 needed.
|
|
210
|
+
if (!d1.flagged && !forced && d1.undetermined.length === 0) {
|
|
211
|
+
return { finalOutput: out1, stagesRun: 1, stage1Decision: d1, forcedStage2: false };
|
|
212
|
+
}
|
|
213
|
+
// Stage 2 triggered. Resolve tier2 lineage first.
|
|
214
|
+
if (deps.lineage !== undefined) {
|
|
215
|
+
const tier2 = await deps.lineage.resolve(deps.startArtifactId);
|
|
216
|
+
if (!tier2.ok) {
|
|
217
|
+
emit({ type: 'lineage_data_corrupt', detail: tier2.error.kind });
|
|
218
|
+
return {
|
|
219
|
+
finalOutput: out1,
|
|
220
|
+
stagesRun: 1,
|
|
221
|
+
stage1Decision: d1,
|
|
222
|
+
forcedStage2: forced,
|
|
223
|
+
stage2Aborted: { reason: 'lineage_error', detail: tier2.error.kind },
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Stage 2: independent re-evaluation with tier2 context.
|
|
228
|
+
// rc-7: do NOT inject out1, d1, or Stage 1 concerns into Stage 2.
|
|
229
|
+
const stage2Ctx = deps.stage2Context ?? deps.stage1Context;
|
|
230
|
+
const out2 = await deps.llm.evaluate(stage2Ctx);
|
|
231
|
+
// False-negative check (forced sample only, Stage 1 not flagged).
|
|
232
|
+
let falseNeg;
|
|
233
|
+
if (forced && !d1.flagged) {
|
|
234
|
+
const newKeys = diffConcernKeys(out2, out1);
|
|
235
|
+
if (newKeys.length > 0) {
|
|
236
|
+
falseNeg = { newConcernKeys: newKeys };
|
|
237
|
+
emit({ type: 'stage1_false_negative', newConcernKeys: newKeys });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
finalOutput: out2,
|
|
242
|
+
stagesRun: 2,
|
|
243
|
+
stage1Decision: d1,
|
|
244
|
+
forcedStage2: forced,
|
|
245
|
+
stage1FalseNegative: falseNeg,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=progressive-evaluator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progressive-evaluator.js","sourceRoot":"","sources":["../../../src/runtime-v2/internalization/progressive-evaluator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 1 — PromptBudgetManager: budgeted context allocation (design §6.3).
|
|
3
|
+
*
|
|
4
|
+
* Pure logic only (Core vs Plugin boundary, `antipattern-core-io`).
|
|
5
|
+
*
|
|
6
|
+
* `allocateContext` takes a manifest + the available field values (as a
|
|
7
|
+
* `ReadonlyMap<string, unknown>`, rc-1) and produces an `AllocatedContext`:
|
|
8
|
+
* which fields fit the budget, which were truncated, which were dropped, and
|
|
9
|
+
* which were absent. It is a PURE allocation function — it does NOT decide
|
|
10
|
+
* whether to fall back to full-predecessor injection; that decision lives in
|
|
11
|
+
* `resolveInjection` (resolve-injection.ts, task 5.10).
|
|
12
|
+
*
|
|
13
|
+
* Key correctness properties (design §6.3 postconditions):
|
|
14
|
+
* - `usedTokens <= budgetTokens`
|
|
15
|
+
* - every dropped/truncated field has a TruncationRecord AND emits a
|
|
16
|
+
* `context_truncated` event (rc-9 — never silently lose a field; the base
|
|
17
|
+
* proposal `break`ed on budget exhaustion, dropping remaining fields
|
|
18
|
+
* without record — ERR-002)
|
|
19
|
+
* - identical input always yields identical output (total order by
|
|
20
|
+
* (rank ASC, path ASC), no randomness, no time dependency)
|
|
21
|
+
* - unknown values go through `safeStringifyPreview` (rc-8) before token
|
|
22
|
+
* estimation
|
|
23
|
+
*
|
|
24
|
+
* budgetTokens scope (design §6.2.1 / §6.3 — MUST be reflected in comments):
|
|
25
|
+
* covers ONLY manifest-declared injection fields (tier0/tier1/tier2). Does NOT
|
|
26
|
+
* include core grounding, runner base instructions, or output-schema
|
|
27
|
+
* descriptions. `usedTokens <= budgetTokens` is an injection-field budget
|
|
28
|
+
* ceiling, NOT a prompt-total-length hard cap.
|
|
29
|
+
*/
|
|
30
|
+
import type { ContextManifest } from './context-manifest.js';
|
|
31
|
+
import type { SummaryRunnerKind } from './artifact-summary.js';
|
|
32
|
+
/** char/4 heuristic; no tokenizer dependency. Monotonically non-decreasing, pure. */
|
|
33
|
+
export declare function estimateTokens(text: string): number;
|
|
34
|
+
/** Max chars of a single field's serialized preview before token estimation. */
|
|
35
|
+
export declare const FIELD_PREVIEW_MAX_CHARS = 600;
|
|
36
|
+
/**
|
|
37
|
+
* Below this remaining budget, a field is dropped whole rather than
|
|
38
|
+
* partially truncated — a partial slice shorter than this carries too little
|
|
39
|
+
* signal to be worth the slot. Expressed in tokens (≈ 4 chars each).
|
|
40
|
+
*/
|
|
41
|
+
export declare const MIN_USEFUL_TOKENS = 8;
|
|
42
|
+
/** Appended to partially-truncated field text so truncation is explicit (rc-9). */
|
|
43
|
+
export declare const TRUNCATION_MARKER = "\u2026[budget-truncated]";
|
|
44
|
+
export interface TruncationRecord {
|
|
45
|
+
readonly fieldPath: string;
|
|
46
|
+
readonly reason: 'budget_exceeded' | 'partially_truncated';
|
|
47
|
+
readonly remainingBudgetTokens: number;
|
|
48
|
+
readonly keptChars: number;
|
|
49
|
+
readonly droppedChars: number;
|
|
50
|
+
}
|
|
51
|
+
export interface AllocatedContext {
|
|
52
|
+
readonly manifestId: string;
|
|
53
|
+
/** Fields that fit (fully or partially) into the budget. */
|
|
54
|
+
readonly fields: Readonly<Record<string, string>>;
|
|
55
|
+
/** Fields dropped/truncated, each with a reason (rc-9). */
|
|
56
|
+
readonly truncated: readonly TruncationRecord[];
|
|
57
|
+
/** Manifest-declared paths not present in the available map. */
|
|
58
|
+
readonly absent: readonly string[];
|
|
59
|
+
readonly usedTokens: number;
|
|
60
|
+
readonly budgetTokens: number;
|
|
61
|
+
}
|
|
62
|
+
export interface ContextTruncatedEvent {
|
|
63
|
+
readonly type: 'context_truncated';
|
|
64
|
+
readonly runnerKind: SummaryRunnerKind;
|
|
65
|
+
readonly manifestId: string;
|
|
66
|
+
readonly fieldPath: string;
|
|
67
|
+
readonly reason: TruncationRecord['reason'];
|
|
68
|
+
readonly remainingBudgetTokens: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Allocate manifest-declared fields into the token budget (design §6.3).
|
|
72
|
+
*
|
|
73
|
+
* Preconditions: `manifest.budgetTokens > 0`; `available` values are `unknown`
|
|
74
|
+
* (rc-1) and are narrowed only via `safeStringifyPreview`.
|
|
75
|
+
*
|
|
76
|
+
* Postconditions:
|
|
77
|
+
* - `usedTokens <= budgetTokens`
|
|
78
|
+
* - every dropped/truncated field has a TruncationRecord and emitted a
|
|
79
|
+
* `context_truncated` event (rc-9)
|
|
80
|
+
* - identical input → identical output (total order, deterministic)
|
|
81
|
+
*
|
|
82
|
+
* Loop invariant while traversing: `usedTokens + remainingBudget === budgetTokens`
|
|
83
|
+
* (until a partial truncate zeroes remaining).
|
|
84
|
+
*
|
|
85
|
+
* NOTE: this function does NOT perform the information-floor fallback (design
|
|
86
|
+
* §6.2.2). The fallback is decided by `resolveInjection` after reading this
|
|
87
|
+
* function's `absent` array.
|
|
88
|
+
*/
|
|
89
|
+
export declare function allocateContext(manifest: ContextManifest, available: ReadonlyMap<string, unknown>, emit: (event: ContextTruncatedEvent) => void): AllocatedContext;
|
|
90
|
+
//# sourceMappingURL=prompt-budget-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-budget-manager.d.ts","sourceRoot":"","sources":["../../../src/runtime-v2/internalization/prompt-budget-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAG/D,qFAAqF;AACrF,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,gFAAgF;AAChF,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAE3C;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,IAAI,CAAC;AAEnC,mFAAmF;AACnF,eAAO,MAAM,iBAAiB,6BAAwB,CAAC;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,iBAAiB,GAAG,qBAAqB,CAAC;IAC3D,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAChD,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,iBAAiB,CAAC;IACvC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC5C,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;CACxC;AASD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,eAAe,EACzB,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,EACvC,IAAI,EAAE,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,GAC3C,gBAAgB,CAsFlB"}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 1 — PromptBudgetManager: budgeted context allocation (design §6.3).
|
|
3
|
+
*
|
|
4
|
+
* Pure logic only (Core vs Plugin boundary, `antipattern-core-io`).
|
|
5
|
+
*
|
|
6
|
+
* `allocateContext` takes a manifest + the available field values (as a
|
|
7
|
+
* `ReadonlyMap<string, unknown>`, rc-1) and produces an `AllocatedContext`:
|
|
8
|
+
* which fields fit the budget, which were truncated, which were dropped, and
|
|
9
|
+
* which were absent. It is a PURE allocation function — it does NOT decide
|
|
10
|
+
* whether to fall back to full-predecessor injection; that decision lives in
|
|
11
|
+
* `resolveInjection` (resolve-injection.ts, task 5.10).
|
|
12
|
+
*
|
|
13
|
+
* Key correctness properties (design §6.3 postconditions):
|
|
14
|
+
* - `usedTokens <= budgetTokens`
|
|
15
|
+
* - every dropped/truncated field has a TruncationRecord AND emits a
|
|
16
|
+
* `context_truncated` event (rc-9 — never silently lose a field; the base
|
|
17
|
+
* proposal `break`ed on budget exhaustion, dropping remaining fields
|
|
18
|
+
* without record — ERR-002)
|
|
19
|
+
* - identical input always yields identical output (total order by
|
|
20
|
+
* (rank ASC, path ASC), no randomness, no time dependency)
|
|
21
|
+
* - unknown values go through `safeStringifyPreview` (rc-8) before token
|
|
22
|
+
* estimation
|
|
23
|
+
*
|
|
24
|
+
* budgetTokens scope (design §6.2.1 / §6.3 — MUST be reflected in comments):
|
|
25
|
+
* covers ONLY manifest-declared injection fields (tier0/tier1/tier2). Does NOT
|
|
26
|
+
* include core grounding, runner base instructions, or output-schema
|
|
27
|
+
* descriptions. `usedTokens <= budgetTokens` is an injection-field budget
|
|
28
|
+
* ceiling, NOT a prompt-total-length hard cap.
|
|
29
|
+
*/
|
|
30
|
+
import { rankOf, declaredFields } from './context-manifest.js';
|
|
31
|
+
import { safeStringifyPreview } from '../adapter/output-repair-contract.js';
|
|
32
|
+
/** char/4 heuristic; no tokenizer dependency. Monotonically non-decreasing, pure. */
|
|
33
|
+
export function estimateTokens(text) {
|
|
34
|
+
return Math.ceil(text.length / 4);
|
|
35
|
+
}
|
|
36
|
+
/** Max chars of a single field's serialized preview before token estimation. */
|
|
37
|
+
export const FIELD_PREVIEW_MAX_CHARS = 600;
|
|
38
|
+
/**
|
|
39
|
+
* Below this remaining budget, a field is dropped whole rather than
|
|
40
|
+
* partially truncated — a partial slice shorter than this carries too little
|
|
41
|
+
* signal to be worth the slot. Expressed in tokens (≈ 4 chars each).
|
|
42
|
+
*/
|
|
43
|
+
export const MIN_USEFUL_TOKENS = 8;
|
|
44
|
+
/** Appended to partially-truncated field text so truncation is explicit (rc-9). */
|
|
45
|
+
export const TRUNCATION_MARKER = '…[budget-truncated]';
|
|
46
|
+
/**
|
|
47
|
+
* Allocate manifest-declared fields into the token budget (design §6.3).
|
|
48
|
+
*
|
|
49
|
+
* Preconditions: `manifest.budgetTokens > 0`; `available` values are `unknown`
|
|
50
|
+
* (rc-1) and are narrowed only via `safeStringifyPreview`.
|
|
51
|
+
*
|
|
52
|
+
* Postconditions:
|
|
53
|
+
* - `usedTokens <= budgetTokens`
|
|
54
|
+
* - every dropped/truncated field has a TruncationRecord and emitted a
|
|
55
|
+
* `context_truncated` event (rc-9)
|
|
56
|
+
* - identical input → identical output (total order, deterministic)
|
|
57
|
+
*
|
|
58
|
+
* Loop invariant while traversing: `usedTokens + remainingBudget === budgetTokens`
|
|
59
|
+
* (until a partial truncate zeroes remaining).
|
|
60
|
+
*
|
|
61
|
+
* NOTE: this function does NOT perform the information-floor fallback (design
|
|
62
|
+
* §6.2.2). The fallback is decided by `resolveInjection` after reading this
|
|
63
|
+
* function's `absent` array.
|
|
64
|
+
*/
|
|
65
|
+
export function allocateContext(manifest, available, emit) {
|
|
66
|
+
const paths = declaredFields(manifest);
|
|
67
|
+
const scored = [];
|
|
68
|
+
const absent = [];
|
|
69
|
+
// Score every present path; record absent ones.
|
|
70
|
+
for (const p of paths) {
|
|
71
|
+
if (!available.has(p)) {
|
|
72
|
+
absent.push(p);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const text = safeStringifyPreview(available.get(p), FIELD_PREVIEW_MAX_CHARS);
|
|
76
|
+
scored.push({
|
|
77
|
+
path: p,
|
|
78
|
+
text,
|
|
79
|
+
tokens: estimateTokens(text),
|
|
80
|
+
rank: rankOf(p, manifest),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
// Total order: (rank ASC, path ASC). Deterministic regardless of map
|
|
84
|
+
// insertion order or tier declaration order.
|
|
85
|
+
scored.sort((a, b) => a.rank - b.rank || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
86
|
+
const fields = {};
|
|
87
|
+
const truncated = [];
|
|
88
|
+
let remaining = manifest.budgetTokens;
|
|
89
|
+
for (const f of scored) {
|
|
90
|
+
if (f.tokens <= remaining) {
|
|
91
|
+
// Fits fully.
|
|
92
|
+
fields[f.path] = f.text;
|
|
93
|
+
remaining -= f.tokens;
|
|
94
|
+
}
|
|
95
|
+
else if (remaining > MIN_USEFUL_TOKENS) {
|
|
96
|
+
// Partial truncate: keep a prefix that fits, mark explicitly.
|
|
97
|
+
const keptChars = Math.max(0, remaining * 4 - TRUNCATION_MARKER.length);
|
|
98
|
+
const kept = f.text.slice(0, keptChars) + TRUNCATION_MARKER;
|
|
99
|
+
fields[f.path] = kept;
|
|
100
|
+
const rec = {
|
|
101
|
+
fieldPath: f.path,
|
|
102
|
+
reason: 'partially_truncated',
|
|
103
|
+
remainingBudgetTokens: remaining,
|
|
104
|
+
keptChars: kept.length,
|
|
105
|
+
droppedChars: f.text.length - kept.length,
|
|
106
|
+
};
|
|
107
|
+
truncated.push(rec);
|
|
108
|
+
emit({
|
|
109
|
+
type: 'context_truncated',
|
|
110
|
+
runnerKind: manifest.runnerKind,
|
|
111
|
+
manifestId: manifest.manifestId,
|
|
112
|
+
fieldPath: f.path,
|
|
113
|
+
reason: rec.reason,
|
|
114
|
+
remainingBudgetTokens: remaining,
|
|
115
|
+
});
|
|
116
|
+
remaining = 0;
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
// Drop whole: remaining budget too small for a useful partial. Do NOT
|
|
120
|
+
// break — keep traversing so every remaining field is recorded (rc-9 /
|
|
121
|
+
// ERR-002: the base proposal broke here, silently losing the rest).
|
|
122
|
+
const rec = {
|
|
123
|
+
fieldPath: f.path,
|
|
124
|
+
reason: 'budget_exceeded',
|
|
125
|
+
remainingBudgetTokens: remaining,
|
|
126
|
+
keptChars: 0,
|
|
127
|
+
droppedChars: f.text.length,
|
|
128
|
+
};
|
|
129
|
+
truncated.push(rec);
|
|
130
|
+
emit({
|
|
131
|
+
type: 'context_truncated',
|
|
132
|
+
runnerKind: manifest.runnerKind,
|
|
133
|
+
manifestId: manifest.manifestId,
|
|
134
|
+
fieldPath: f.path,
|
|
135
|
+
reason: rec.reason,
|
|
136
|
+
remainingBudgetTokens: remaining,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
manifestId: manifest.manifestId,
|
|
142
|
+
fields,
|
|
143
|
+
truncated,
|
|
144
|
+
absent,
|
|
145
|
+
usedTokens: manifest.budgetTokens - remaining,
|
|
146
|
+
budgetTokens: manifest.budgetTokens,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=prompt-budget-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-budget-manager.js","sourceRoot":"","sources":["../../../src/runtime-v2/internalization/prompt-budget-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 1 — information-floor fallback wrapper (design §6.2.2).
|
|
3
|
+
*
|
|
4
|
+
* Pure logic only (Core vs Plugin boundary).
|
|
5
|
+
*
|
|
6
|
+
* Layer 1's semantics are "focus + budget control WITHOUT going below the
|
|
7
|
+
* information level of the flag-off baseline" (INV-FLOOR). Today dreamer
|
|
8
|
+
* already receives the predecessor's FULL contentJson (F13). Switching to a
|
|
9
|
+
* manifest-selected subset could make the prompt thinner — the opposite of the
|
|
10
|
+
* goal. So `resolveInjection` runs `allocateContext` first, then decides
|
|
11
|
+
* whether the resolved context is too sparse to use; if so it falls back to
|
|
12
|
+
* the legacy full-predecessor injection (NOT a thinner prompt) and emits a
|
|
13
|
+
* structured `manifest_resolution_insufficient` event (rc-9).
|
|
14
|
+
*
|
|
15
|
+
* The fallback decision lives HERE (the caller), not inside `allocateContext`
|
|
16
|
+
* — `allocateContext` stays a pure allocation function. The `absent` array
|
|
17
|
+
* `allocateContext` returns doubles as the fallback-judgement input.
|
|
18
|
+
*/
|
|
19
|
+
import type { ContextManifest } from './context-manifest.js';
|
|
20
|
+
import { type AllocatedContext, type ContextTruncatedEvent } from './prompt-budget-manager.js';
|
|
21
|
+
/** Initial value; dogfood-tunable (design §6.2.2). */
|
|
22
|
+
export declare const MANIFEST_ABSENT_RATIO_THRESHOLD = 0.5;
|
|
23
|
+
export interface ManifestResolutionInsufficientEvent {
|
|
24
|
+
readonly type: 'manifest_resolution_insufficient';
|
|
25
|
+
readonly runnerKind: ContextManifest['runnerKind'];
|
|
26
|
+
readonly manifestId: string;
|
|
27
|
+
readonly absentCount: number;
|
|
28
|
+
readonly declaredCount: number;
|
|
29
|
+
readonly absentRatio: number;
|
|
30
|
+
readonly fallback: 'full_predecessor_injection';
|
|
31
|
+
}
|
|
32
|
+
export type ResolveInjectionEmit = ContextTruncatedEvent | ManifestResolutionInsufficientEvent;
|
|
33
|
+
export type ResolveInjectionResult = {
|
|
34
|
+
readonly kind: 'focused';
|
|
35
|
+
readonly allocated: AllocatedContext;
|
|
36
|
+
readonly fellBack: false;
|
|
37
|
+
} | {
|
|
38
|
+
readonly kind: 'fallback';
|
|
39
|
+
readonly allocated: AllocatedContext;
|
|
40
|
+
readonly fellBack: true;
|
|
41
|
+
readonly reason: 'empty_allocation' | 'tier1_all_absent' | 'absent_ratio_exceeded';
|
|
42
|
+
readonly absentRatio: number;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the injection for a runner: allocate per the manifest, then decide
|
|
46
|
+
* whether to fall back to the legacy full-predecessor injection (design §6.2.2).
|
|
47
|
+
*
|
|
48
|
+
* The caller is responsible for:
|
|
49
|
+
* - providing the `available` map (built from the loaded predecessor's
|
|
50
|
+
* summary/predecessorSummary — Layer 0)
|
|
51
|
+
* - acting on `result.kind`: 'focused' → use `allocated.fields`;
|
|
52
|
+
* 'fallback' → use the legacy `predecessorOutput` (F13) instead
|
|
53
|
+
* - passing through ALL events (both `context_truncated` from the allocation
|
|
54
|
+
* attempt AND `manifest_resolution_insufficient` when fallback fires)
|
|
55
|
+
*
|
|
56
|
+
* When the fallback fires, the `allocated` is still returned (with its
|
|
57
|
+
* `context_truncated` events already emitted) so Layer 3 can surface "what
|
|
58
|
+
* would have been truncated if we'd used the manifest" alongside the
|
|
59
|
+
* `manifest_resolution_insufficient` degradation.
|
|
60
|
+
*/
|
|
61
|
+
export declare function resolveInjection(manifest: ContextManifest, available: ReadonlyMap<string, unknown>, emit: (event: ResolveInjectionEmit) => void): ResolveInjectionResult;
|
|
62
|
+
//# sourceMappingURL=resolve-injection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve-injection.d.ts","sourceRoot":"","sources":["../../../src/runtime-v2/internalization/resolve-injection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC3B,MAAM,4BAA4B,CAAC;AAEpC,sDAAsD;AACtD,eAAO,MAAM,+BAA+B,MAAM,CAAC;AAEnD,MAAM,WAAW,mCAAmC;IAClD,QAAQ,CAAC,IAAI,EAAE,kCAAkC,CAAC;IAClD,QAAQ,CAAC,UAAU,EAAE,eAAe,CAAC,YAAY,CAAC,CAAC;IACnD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,4BAA4B,CAAC;CACjD;AAED,MAAM,MAAM,oBAAoB,GAC5B,qBAAqB,GACrB,mCAAmC,CAAC;AAExC,MAAM,MAAM,sBAAsB,GAC9B;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC;CAC1B,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,GAAG,uBAAuB,CAAC;IACnF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEN;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,eAAe,EACzB,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,EACvC,IAAI,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAC1C,sBAAsB,CA0CxB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 1 — information-floor fallback wrapper (design §6.2.2).
|
|
3
|
+
*
|
|
4
|
+
* Pure logic only (Core vs Plugin boundary).
|
|
5
|
+
*
|
|
6
|
+
* Layer 1's semantics are "focus + budget control WITHOUT going below the
|
|
7
|
+
* information level of the flag-off baseline" (INV-FLOOR). Today dreamer
|
|
8
|
+
* already receives the predecessor's FULL contentJson (F13). Switching to a
|
|
9
|
+
* manifest-selected subset could make the prompt thinner — the opposite of the
|
|
10
|
+
* goal. So `resolveInjection` runs `allocateContext` first, then decides
|
|
11
|
+
* whether the resolved context is too sparse to use; if so it falls back to
|
|
12
|
+
* the legacy full-predecessor injection (NOT a thinner prompt) and emits a
|
|
13
|
+
* structured `manifest_resolution_insufficient` event (rc-9).
|
|
14
|
+
*
|
|
15
|
+
* The fallback decision lives HERE (the caller), not inside `allocateContext`
|
|
16
|
+
* — `allocateContext` stays a pure allocation function. The `absent` array
|
|
17
|
+
* `allocateContext` returns doubles as the fallback-judgement input.
|
|
18
|
+
*/
|
|
19
|
+
import { declaredFields } from './context-manifest.js';
|
|
20
|
+
import { allocateContext, } from './prompt-budget-manager.js';
|
|
21
|
+
/** Initial value; dogfood-tunable (design §6.2.2). */
|
|
22
|
+
export const MANIFEST_ABSENT_RATIO_THRESHOLD = 0.5;
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the injection for a runner: allocate per the manifest, then decide
|
|
25
|
+
* whether to fall back to the legacy full-predecessor injection (design §6.2.2).
|
|
26
|
+
*
|
|
27
|
+
* The caller is responsible for:
|
|
28
|
+
* - providing the `available` map (built from the loaded predecessor's
|
|
29
|
+
* summary/predecessorSummary — Layer 0)
|
|
30
|
+
* - acting on `result.kind`: 'focused' → use `allocated.fields`;
|
|
31
|
+
* 'fallback' → use the legacy `predecessorOutput` (F13) instead
|
|
32
|
+
* - passing through ALL events (both `context_truncated` from the allocation
|
|
33
|
+
* attempt AND `manifest_resolution_insufficient` when fallback fires)
|
|
34
|
+
*
|
|
35
|
+
* When the fallback fires, the `allocated` is still returned (with its
|
|
36
|
+
* `context_truncated` events already emitted) so Layer 3 can surface "what
|
|
37
|
+
* would have been truncated if we'd used the manifest" alongside the
|
|
38
|
+
* `manifest_resolution_insufficient` degradation.
|
|
39
|
+
*/
|
|
40
|
+
export function resolveInjection(manifest, available, emit) {
|
|
41
|
+
const allocated = allocateContext(manifest, available, (e) => emit(e));
|
|
42
|
+
const declared = declaredFields(manifest);
|
|
43
|
+
const declaredCount = declared.length;
|
|
44
|
+
const absentCount = allocated.absent.length;
|
|
45
|
+
// Guard against divide-by-zero: a manifest with no declared fields is itself
|
|
46
|
+
// malformed (validateManifest requires budget>0 but not non-empty tiers); we
|
|
47
|
+
// treat an empty declaration as an empty-allocation fallback.
|
|
48
|
+
const absentRatio = declaredCount > 0 ? absentCount / declaredCount : 1;
|
|
49
|
+
const fallbackEvent = () => ({
|
|
50
|
+
type: 'manifest_resolution_insufficient',
|
|
51
|
+
runnerKind: manifest.runnerKind,
|
|
52
|
+
manifestId: manifest.manifestId,
|
|
53
|
+
absentCount,
|
|
54
|
+
declaredCount,
|
|
55
|
+
absentRatio,
|
|
56
|
+
fallback: 'full_predecessor_injection',
|
|
57
|
+
});
|
|
58
|
+
// Fallback trigger 1: nothing was allocated (empty allocation).
|
|
59
|
+
if (Object.keys(allocated.fields).length === 0) {
|
|
60
|
+
emit(fallbackEvent());
|
|
61
|
+
return { kind: 'fallback', allocated, fellBack: true, reason: 'empty_allocation', absentRatio };
|
|
62
|
+
}
|
|
63
|
+
// Fallback trigger 2: all tier1-declared fields are absent. tier1 holds the
|
|
64
|
+
// bulk of the structured context; if none of it resolved, the focused
|
|
65
|
+
// context is too thin to beat the legacy full-predecessor injection.
|
|
66
|
+
const tier1AllAbsent = manifest.tier1.length > 0 && manifest.tier1.every((p) => allocated.absent.includes(p));
|
|
67
|
+
if (tier1AllAbsent) {
|
|
68
|
+
emit(fallbackEvent());
|
|
69
|
+
return { kind: 'fallback', allocated, fellBack: true, reason: 'tier1_all_absent', absentRatio };
|
|
70
|
+
}
|
|
71
|
+
// Fallback trigger 3: absent ratio exceeds the threshold.
|
|
72
|
+
if (absentRatio > MANIFEST_ABSENT_RATIO_THRESHOLD) {
|
|
73
|
+
emit(fallbackEvent());
|
|
74
|
+
return { kind: 'fallback', allocated, fellBack: true, reason: 'absent_ratio_exceeded', absentRatio };
|
|
75
|
+
}
|
|
76
|
+
return { kind: 'focused', allocated, fellBack: false };
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=resolve-injection.js.map
|