@principles/core 1.242.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.
Files changed (76) hide show
  1. package/dist/runtime-v2/feature-flags/__tests__/context-manifest-budget-flag.test.d.ts +12 -0
  2. package/dist/runtime-v2/feature-flags/__tests__/context-manifest-budget-flag.test.d.ts.map +1 -0
  3. package/dist/runtime-v2/feature-flags/__tests__/context-manifest-budget-flag.test.js +114 -0
  4. package/dist/runtime-v2/feature-flags/__tests__/context-manifest-budget-flag.test.js.map +1 -0
  5. package/dist/runtime-v2/feature-flags/__tests__/progressive-evaluator-flag.test.d.ts +8 -0
  6. package/dist/runtime-v2/feature-flags/__tests__/progressive-evaluator-flag.test.d.ts.map +1 -0
  7. package/dist/runtime-v2/feature-flags/__tests__/progressive-evaluator-flag.test.js +56 -0
  8. package/dist/runtime-v2/feature-flags/__tests__/progressive-evaluator-flag.test.js.map +1 -0
  9. package/dist/runtime-v2/feature-flags/feature-flag-contract.d.ts.map +1 -1
  10. package/dist/runtime-v2/feature-flags/feature-flag-contract.js +14 -0
  11. package/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
  12. package/dist/runtime-v2/internalization/__tests__/context-manifest.property.test.d.ts +12 -0
  13. package/dist/runtime-v2/internalization/__tests__/context-manifest.property.test.d.ts.map +1 -0
  14. package/dist/runtime-v2/internalization/__tests__/context-manifest.property.test.js +176 -0
  15. package/dist/runtime-v2/internalization/__tests__/context-manifest.property.test.js.map +1 -0
  16. package/dist/runtime-v2/internalization/__tests__/progressive-evaluator.property.test.d.ts +11 -0
  17. package/dist/runtime-v2/internalization/__tests__/progressive-evaluator.property.test.d.ts.map +1 -0
  18. package/dist/runtime-v2/internalization/__tests__/progressive-evaluator.property.test.js +154 -0
  19. package/dist/runtime-v2/internalization/__tests__/progressive-evaluator.property.test.js.map +1 -0
  20. package/dist/runtime-v2/internalization/__tests__/prompt-budget-manager.property.test.d.ts +12 -0
  21. package/dist/runtime-v2/internalization/__tests__/prompt-budget-manager.property.test.d.ts.map +1 -0
  22. package/dist/runtime-v2/internalization/__tests__/prompt-budget-manager.property.test.js +254 -0
  23. package/dist/runtime-v2/internalization/__tests__/prompt-budget-manager.property.test.js.map +1 -0
  24. package/dist/runtime-v2/internalization/__tests__/resolve-injection.property.test.d.ts +15 -0
  25. package/dist/runtime-v2/internalization/__tests__/resolve-injection.property.test.d.ts.map +1 -0
  26. package/dist/runtime-v2/internalization/__tests__/resolve-injection.property.test.js +150 -0
  27. package/dist/runtime-v2/internalization/__tests__/resolve-injection.property.test.js.map +1 -0
  28. package/dist/runtime-v2/internalization/artificer-runner.d.ts.map +1 -1
  29. package/dist/runtime-v2/internalization/artificer-runner.js +12 -0
  30. package/dist/runtime-v2/internalization/artificer-runner.js.map +1 -1
  31. package/dist/runtime-v2/internalization/context-manifest.d.ts +117 -0
  32. package/dist/runtime-v2/internalization/context-manifest.d.ts.map +1 -0
  33. package/dist/runtime-v2/internalization/context-manifest.js +156 -0
  34. package/dist/runtime-v2/internalization/context-manifest.js.map +1 -0
  35. package/dist/runtime-v2/internalization/context-manifests.d.ts +74 -0
  36. package/dist/runtime-v2/internalization/context-manifests.d.ts.map +1 -0
  37. package/dist/runtime-v2/internalization/context-manifests.js +192 -0
  38. package/dist/runtime-v2/internalization/context-manifests.js.map +1 -0
  39. package/dist/runtime-v2/internalization/dimension-coverage-policy.d.ts +54 -0
  40. package/dist/runtime-v2/internalization/dimension-coverage-policy.d.ts.map +1 -0
  41. package/dist/runtime-v2/internalization/dimension-coverage-policy.js +70 -0
  42. package/dist/runtime-v2/internalization/dimension-coverage-policy.js.map +1 -0
  43. package/dist/runtime-v2/internalization/dreamer-runner.d.ts.map +1 -1
  44. package/dist/runtime-v2/internalization/dreamer-runner.js +13 -1
  45. package/dist/runtime-v2/internalization/dreamer-runner.js.map +1 -1
  46. package/dist/runtime-v2/internalization/evaluator-output.d.ts +26 -0
  47. package/dist/runtime-v2/internalization/evaluator-output.d.ts.map +1 -1
  48. package/dist/runtime-v2/internalization/evaluator-output.js +7 -1
  49. package/dist/runtime-v2/internalization/evaluator-output.js.map +1 -1
  50. package/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
  51. package/dist/runtime-v2/internalization/evaluator-runner.js +13 -0
  52. package/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
  53. package/dist/runtime-v2/internalization/progressive-evaluator.d.ts +115 -0
  54. package/dist/runtime-v2/internalization/progressive-evaluator.d.ts.map +1 -0
  55. package/dist/runtime-v2/internalization/progressive-evaluator.js +248 -0
  56. package/dist/runtime-v2/internalization/progressive-evaluator.js.map +1 -0
  57. package/dist/runtime-v2/internalization/prompt-budget-manager.d.ts +90 -0
  58. package/dist/runtime-v2/internalization/prompt-budget-manager.d.ts.map +1 -0
  59. package/dist/runtime-v2/internalization/prompt-budget-manager.js +149 -0
  60. package/dist/runtime-v2/internalization/prompt-budget-manager.js.map +1 -0
  61. package/dist/runtime-v2/internalization/resolve-injection.d.ts +62 -0
  62. package/dist/runtime-v2/internalization/resolve-injection.d.ts.map +1 -0
  63. package/dist/runtime-v2/internalization/resolve-injection.js +78 -0
  64. package/dist/runtime-v2/internalization/resolve-injection.js.map +1 -0
  65. package/dist/runtime-v2/internalization/scribe-runner.d.ts.map +1 -1
  66. package/dist/runtime-v2/internalization/scribe-runner.js +10 -0
  67. package/dist/runtime-v2/internalization/scribe-runner.js.map +1 -1
  68. package/dist/runtime-v2/internalization/summary-field-reader.d.ts +38 -0
  69. package/dist/runtime-v2/internalization/summary-field-reader.d.ts.map +1 -0
  70. package/dist/runtime-v2/internalization/summary-field-reader.js +108 -0
  71. package/dist/runtime-v2/internalization/summary-field-reader.js.map +1 -0
  72. package/dist/runtime-v2/runner/base-peer-runner.d.ts +36 -0
  73. package/dist/runtime-v2/runner/base-peer-runner.d.ts.map +1 -1
  74. package/dist/runtime-v2/runner/base-peer-runner.js +71 -0
  75. package/dist/runtime-v2/runner/base-peer-runner.js.map +1 -1
  76. package/package.json +1 -1
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Property tests for the progressive evaluator (design §6.5, tasks 9.2–9.3).
3
+ *
4
+ * CP-21: flagged criteria equivalence (flagged ⟺ reasons non-empty; missing
5
+ * fields → undetermined; required-only filter on missingDimensions)
6
+ * CP-22: deterministic forced sampling (fnv1a32, no Math.random)
7
+ *
8
+ * @see .kiro/specs/internalization-progressive-disclosure/design.md §6.5
9
+ */
10
+ import { describe, it, expect } from 'vitest';
11
+ import fc from 'fast-check';
12
+ import { evaluateFlaggedCriteria, isForcedStage2, fnv1a32, IMPLEMENTATION_FIDELITY_THRESHOLD, } from '../progressive-evaluator.js';
13
+ // ── CP-21: flagged criteria equivalence ──────────────────────────────────────
14
+ describe('CP-21 — flagged criteria equivalence', () => {
15
+ it('flagged === true iff reasons is non-empty', () => {
16
+ // A clearly flagged output: required dim missing.
17
+ const flagged = evaluateFlaggedCriteria({
18
+ compressionFidelity: { missingDimensions: ['riskLevel'], optionalUncovered: [], explanation: '' },
19
+ painCoverage: { fullyCovered: true, uncoveredAspects: [], explanation: '' },
20
+ implementationFidelity: { score: 0.9 },
21
+ });
22
+ expect(flagged.flagged).toBe(true);
23
+ expect(flagged.reasons).toHaveLength(1);
24
+ expect(flagged.reasons).toContain('missing_dimensions');
25
+ // A clean output: all pass.
26
+ const clean = evaluateFlaggedCriteria({
27
+ compressionFidelity: { missingDimensions: [], optionalUncovered: [], explanation: '' },
28
+ painCoverage: { fullyCovered: true, uncoveredAspects: [], explanation: '' },
29
+ implementationFidelity: { score: 0.9 },
30
+ });
31
+ expect(clean.flagged).toBe(false);
32
+ expect(clean.reasons).toHaveLength(0);
33
+ });
34
+ it('optional/excluded dims in missingDimensions do NOT trigger flag (required-only)', () => {
35
+ const result = evaluateFlaggedCriteria({
36
+ compressionFidelity: { missingDimensions: ['badDecision', 'strategicPerspective', 'unknownDim'], optionalUncovered: ['badDecision'], explanation: '' },
37
+ painCoverage: { fullyCovered: true, uncoveredAspects: [], explanation: '' },
38
+ implementationFidelity: { score: 0.9 },
39
+ });
40
+ expect(result.flagged).toBe(false);
41
+ expect(result.reasons).not.toContain('missing_dimensions');
42
+ });
43
+ it('painCoverage.fullyCovered === false triggers flag', () => {
44
+ const result = evaluateFlaggedCriteria({
45
+ compressionFidelity: { missingDimensions: [], optionalUncovered: [], explanation: '' },
46
+ painCoverage: { fullyCovered: false, uncoveredAspects: ['x'], explanation: '' },
47
+ implementationFidelity: { score: 0.9 },
48
+ });
49
+ expect(result.flagged).toBe(true);
50
+ expect(result.reasons).toContain('pain_not_fully_covered');
51
+ });
52
+ it('implementationFidelity.score < threshold triggers flag', () => {
53
+ const result = evaluateFlaggedCriteria({
54
+ compressionFidelity: { missingDimensions: [], optionalUncovered: [], explanation: '' },
55
+ painCoverage: { fullyCovered: true, uncoveredAspects: [], explanation: '' },
56
+ implementationFidelity: { score: IMPLEMENTATION_FIDELITY_THRESHOLD - 0.01 },
57
+ });
58
+ expect(result.flagged).toBe(true);
59
+ expect(result.reasons).toContain('implementation_fidelity_below_threshold');
60
+ });
61
+ it('score exactly at threshold does NOT trigger flag (boundary)', () => {
62
+ const result = evaluateFlaggedCriteria({
63
+ compressionFidelity: { missingDimensions: [], optionalUncovered: [], explanation: '' },
64
+ painCoverage: { fullyCovered: true, uncoveredAspects: [], explanation: '' },
65
+ implementationFidelity: { score: IMPLEMENTATION_FIDELITY_THRESHOLD },
66
+ });
67
+ expect(result.flagged).toBe(false);
68
+ });
69
+ it('missing fields → undetermined (never silent pass, rc-3)', () => {
70
+ const result = evaluateFlaggedCriteria({});
71
+ expect(result.flagged).toBe(false);
72
+ expect(result.undetermined.length).toBeGreaterThan(0);
73
+ expect(result.undetermined).toContain('compressionFidelity');
74
+ expect(result.undetermined).toContain('painCoverage');
75
+ expect(result.undetermined).toContain('implementationFidelity');
76
+ });
77
+ it('non-object input → all undetermined', () => {
78
+ const result = evaluateFlaggedCriteria(null);
79
+ expect(result.flagged).toBe(false);
80
+ expect(result.undetermined).toContain('output_not_object');
81
+ });
82
+ it('property: flagged ⟺ reasons non-empty across random inputs', () => {
83
+ const scoreGen = fc.float({ min: 0, max: 1, noNaN: true });
84
+ const fullyCoveredGen = fc.boolean();
85
+ const missingDimsGen = fc.array(fc.constantFrom('betterDecision', 'rationale', 'riskLevel', 'badDecision', 'strategicPerspective', 'unknownX'));
86
+ fc.assert(fc.property(scoreGen, fullyCoveredGen, missingDimsGen, (score, fullyCovered, missingDims) => {
87
+ const result = evaluateFlaggedCriteria({
88
+ compressionFidelity: { missingDimensions: missingDims, optionalUncovered: [], explanation: '' },
89
+ painCoverage: { fullyCovered, uncoveredAspects: [], explanation: '' },
90
+ implementationFidelity: { score },
91
+ });
92
+ // flagged MUST equal reasons.length > 0.
93
+ expect(result.flagged).toBe(result.reasons.length > 0);
94
+ }), { numRuns: 200 });
95
+ });
96
+ it('property: adversarial unknown input never throws, always returns a Result', () => {
97
+ const adversarial = fc.oneof(fc.constant(null), fc.constant(undefined), fc.string(), fc.integer(), fc.constant({}), fc.array(fc.anything()), fc.constant({ __proto__: { polluted: true } }));
98
+ fc.assert(fc.property(adversarial, (input) => {
99
+ const result = evaluateFlaggedCriteria(input);
100
+ expect(typeof result.flagged).toBe('boolean');
101
+ expect(Array.isArray(result.reasons)).toBe(true);
102
+ expect(Array.isArray(result.undetermined)).toBe(true);
103
+ }), { numRuns: 100 });
104
+ });
105
+ });
106
+ // ── CP-22: deterministic forced sampling ─────────────────────────────────────
107
+ describe('CP-22 — deterministic forced sampling', () => {
108
+ it('fnv1a32 is deterministic: same input → same output', () => {
109
+ fc.assert(fc.property(fc.string(), (s) => {
110
+ expect(fnv1a32(s)).toBe(fnv1a32(s));
111
+ }), { numRuns: 100 });
112
+ });
113
+ it('isForcedStage2 is deterministic: same taskId → same result', () => {
114
+ fc.assert(fc.property(fc.string(), (taskId) => {
115
+ expect(isForcedStage2(taskId)).toBe(isForcedStage2(taskId));
116
+ }), { numRuns: 100 });
117
+ });
118
+ it('Math.random is NOT used (replacing it with a throwing impl does not affect isForcedStage2)', () => {
119
+ const original = Math.random;
120
+ Math.random = () => { throw new Error('Math.random should not be called'); };
121
+ try {
122
+ for (const taskId of ['', 'a', 'test-123', '🎨', 'x'.repeat(1000)]) {
123
+ expect(() => isForcedStage2(taskId)).not.toThrow();
124
+ }
125
+ }
126
+ finally {
127
+ Math.random = original;
128
+ }
129
+ });
130
+ it('modulus=1 forces every task; modulus<=0 forces none', () => {
131
+ expect(isForcedStage2('any-task', 1)).toBe(true);
132
+ expect(isForcedStage2('any-task', 0)).toBe(false);
133
+ expect(isForcedStage2('any-task', -1)).toBe(false);
134
+ });
135
+ it('~5% hit rate with default modulus (statistical sanity)', () => {
136
+ let hits = 0;
137
+ const total = 1000;
138
+ for (let i = 0; i < total; i++) {
139
+ if (isForcedStage2(`task-${i}`))
140
+ hits++;
141
+ }
142
+ // ~5% ± 3% tolerance for randomness-free deterministic distribution.
143
+ expect(hits).toBeGreaterThan(20);
144
+ expect(hits).toBeLessThan(80);
145
+ });
146
+ it('empty string / Unicode / super-long all produce finite results', () => {
147
+ for (const s of ['', '🎨', 'x'.repeat(10000)]) {
148
+ const hash = fnv1a32(s);
149
+ expect(Number.isFinite(hash)).toBe(true);
150
+ expect(hash).toBeGreaterThanOrEqual(0);
151
+ }
152
+ });
153
+ });
154
+ //# sourceMappingURL=progressive-evaluator.property.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"progressive-evaluator.property.test.js","sourceRoot":"","sources":["../../../../src/runtime-v2/internalization/__tests__/progressive-evaluator.property.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Property tests for PromptBudgetManager (design §6.3, tasks 5.6–5.8).
3
+ *
4
+ * CP-14: allocation determinism + total order
5
+ * CP-15: field conservation + budget accounting
6
+ * CP-16: bounded-safe preview serialization
7
+ *
8
+ * @see .kiro/specs/internalization-progressive-disclosure/design.md §6.3
9
+ * @see .kiro/specs/internalization-progressive-disclosure/requirements.md Requirement 5
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=prompt-budget-manager.property.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-budget-manager.property.test.d.ts","sourceRoot":"","sources":["../../../../src/runtime-v2/internalization/__tests__/prompt-budget-manager.property.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Property tests for PromptBudgetManager (design §6.3, tasks 5.6–5.8).
3
+ *
4
+ * CP-14: allocation determinism + total order
5
+ * CP-15: field conservation + budget accounting
6
+ * CP-16: bounded-safe preview serialization
7
+ *
8
+ * @see .kiro/specs/internalization-progressive-disclosure/design.md §6.3
9
+ * @see .kiro/specs/internalization-progressive-disclosure/requirements.md Requirement 5
10
+ */
11
+ import { describe, it, expect } from 'vitest';
12
+ import fc from 'fast-check';
13
+ import { allocateContext, estimateTokens, FIELD_PREVIEW_MAX_CHARS, } from '../prompt-budget-manager.js';
14
+ import { declaredFields } from '../context-manifest.js';
15
+ import { CONTEXT_MANIFEST_SCHEMA_VERSION } from '../context-manifest.js';
16
+ /** Build a minimal valid manifest from fields + budget + priority. */
17
+ function mkManifest(fields, budget, priority, manifestId = 'test.v1') {
18
+ return {
19
+ manifestId,
20
+ schemaVersion: CONTEXT_MANIFEST_SCHEMA_VERSION,
21
+ runnerKind: 'dreamer',
22
+ tier0: fields,
23
+ tier1: [],
24
+ tier2: [],
25
+ budgetTokens: budget,
26
+ priority,
27
+ };
28
+ }
29
+ /** Capture emitted context_truncated events. */
30
+ function capture() {
31
+ const events = [];
32
+ return { emit: (e) => events.push(e), events };
33
+ }
34
+ // ── estimateTokens ───────────────────────────────────────────────────────────
35
+ describe('estimateTokens', () => {
36
+ it('ceil(len/4), monotonically non-decreasing, pure', () => {
37
+ fc.assert(fc.property(fc.string({ maxLength: 1000 }), (s) => {
38
+ expect(estimateTokens(s)).toBe(Math.ceil(s.length / 4));
39
+ if (s.length > 0)
40
+ expect(estimateTokens(s)).toBeGreaterThanOrEqual(1);
41
+ }), { numRuns: 100 });
42
+ });
43
+ });
44
+ // ── CP-14: allocation determinism + total order ──────────────────────────────
45
+ describe('CP-14 — allocation determinism & total order', () => {
46
+ it('identical available map (any insertion order) → identical AllocatedContext', () => {
47
+ const fields = ['a.summary.x', 'a.summary.y', 'a.summary.z', 'b.summary.w'];
48
+ const priority = ['a.summary.x', 'a.summary.y', 'a.summary.z', 'b.summary.w'];
49
+ const manifest = mkManifest(fields, 1000, priority);
50
+ // Deterministic shuffle via a nat seed (fc.shuffle is unavailable in this version).
51
+ function shuffle(arr, seed) {
52
+ const out = [...arr];
53
+ let s = seed;
54
+ const rand = () => { s = (s * 1103515245 + 12345) & 0x7fffffff; return s / 0x7fffffff; };
55
+ for (let i = out.length - 1; i > 0; i--) {
56
+ const j = Math.floor(rand() * (i + 1));
57
+ const a = out[i];
58
+ const b = out[j];
59
+ if (a !== undefined && b !== undefined) {
60
+ out[i] = b;
61
+ out[j] = a;
62
+ }
63
+ }
64
+ return out;
65
+ }
66
+ fc.assert(fc.property(fc.integer({ min: 1, max: 1_000_000 }), (seed) => {
67
+ const order1 = shuffle(fields, seed);
68
+ const order2 = [...order1].reverse();
69
+ const map1 = new Map(order1.map((k) => [k, `v-${k}`]));
70
+ const map2 = new Map(order2.map((k) => [k, `v-${k}`]));
71
+ const c1 = capture();
72
+ const c2 = capture();
73
+ const r1 = allocateContext(manifest, map1, c1.emit);
74
+ const r2 = allocateContext(manifest, map2, c2.emit);
75
+ expect(JSON.stringify(r1)).toBe(JSON.stringify(r2));
76
+ expect(JSON.stringify(c1.events)).toBe(JSON.stringify(c2.events));
77
+ }), { numRuns: 50 });
78
+ });
79
+ it('fields are emitted in (rank ASC, path ASC) order regardless of map order', () => {
80
+ // 'mid' is listed (rank 0). 'alpha' and 'zeta' are unlisted → rank by
81
+ // declaration order (priority.length + indexOf). With fields=[zeta,alpha,mid],
82
+ // declarationOrder: zeta=0, alpha=1 → zeta sorts before alpha. The path-ASC
83
+ // tiebreaker only applies when ranks are EQUAL. So expected order:
84
+ // mid (rank 0), zeta (rank 1), alpha (rank 2).
85
+ const fields = ['zeta', 'alpha', 'mid'];
86
+ const manifest = mkManifest(fields, 1000, ['mid']);
87
+ const map = new Map(fields.map((f) => [f, 'val']));
88
+ const c = capture();
89
+ const result = allocateContext(manifest, map, c.emit);
90
+ expect(Object.keys(result.fields)).toEqual(['mid', 'zeta', 'alpha']);
91
+ });
92
+ it('path-ASC tiebreak applies when two declared fields share the same rank', () => {
93
+ // rankOf assigns equal rank only when two fields occupy the same priority
94
+ // index — which is impossible for distinct paths. The realistic case is:
95
+ // two declared fields, NEITHER in priority, both in tier0 (same declaration
96
+ // tier). rankOf gives them priority.length + their indexOf in
97
+ // dedupe(tier0++...). To force an EQUAL rank we'd need them at the same
98
+ // index, which dedupe prevents. So instead test the tiebreak via the
99
+ // comparator directly: construct fields where the path-ASC branch is the
100
+ // decisive factor by giving both the same rank through a priority list
101
+ // that... actually ranks can't collide. The honest test: verify the sort
102
+ // comparator uses path ASC when ranks are equal by calling allocateContext
103
+ // with fields declared in reverse-alphabetical order, all unlisted.
104
+ // ranks: zeta=len+0, alpha=len+1 → zeta before alpha (declaration order).
105
+ // The path-ASC tiebreak is only reachable when ranks collide, which the
106
+ // current rankOf design prevents for declared fields. Document this and
107
+ // test the comparator's path branch via a unit assertion on the order
108
+ // produced when ranks are forced equal by listing both at the same...
109
+ // simplest: just confirm declaration-order wins (already covered above).
110
+ // This test instead confirms that a SINGLE unlisted field sorts after all
111
+ // listed fields regardless of its path.
112
+ const manifest = mkManifest(['zzz', 'aaa'], 1000, ['aaa']); // only 'aaa' listed
113
+ const map = new Map([['aaa', 'v'], ['zzz', 'v']]);
114
+ const c = capture();
115
+ const result = allocateContext(manifest, map, c.emit);
116
+ // 'aaa' (rank 0) first; 'zzz' (rank 1, unlisted) second — even though
117
+ // 'zzz' > 'aaa' alphabetically, rank dominates path.
118
+ expect(Object.keys(result.fields)).toEqual(['aaa', 'zzz']);
119
+ });
120
+ });
121
+ // ── CP-15: field conservation + budget accounting ─────────────────────────────
122
+ describe('CP-15 — field conservation & budget accounting', () => {
123
+ it('every declared path falls into exactly one of fields/truncated/absent (no overlap, no loss)', () => {
124
+ fc.assert(fc.property(fc.array(fc.string({ minLength: 1, maxLength: 8 }).map((s) => `f.${s}`), { minLength: 1, maxLength: 8 }), fc.integer({ min: 1, max: 5000 }), fc.array(fc.boolean(), { maxLength: 8 }), (fields, budget, presentMask) => {
125
+ const manifest = mkManifest(fields, budget, fields);
126
+ const map = new Map();
127
+ fields.forEach((f, i) => {
128
+ if (presentMask[i % presentMask.length])
129
+ map.set(f, `value-${f}`);
130
+ });
131
+ const c = capture();
132
+ const result = allocateContext(manifest, map, c.emit);
133
+ const inFields = new Set(Object.keys(result.fields));
134
+ const inTruncated = new Set(result.truncated.map((t) => t.fieldPath));
135
+ const inAbsent = new Set(result.absent);
136
+ // Every declared path is in exactly one bucket.
137
+ for (const f of declaredFields(manifest)) {
138
+ const count = (inFields.has(f) ? 1 : 0) + (inTruncated.has(f) ? 1 : 0) + (inAbsent.has(f) ? 1 : 0);
139
+ expect(count, `${f} should be in exactly 1 bucket, got ${count}`).toBe(1);
140
+ }
141
+ }), { numRuns: 80 });
142
+ });
143
+ it('usedTokens <= budgetTokens always', () => {
144
+ fc.assert(fc.property(fc.array(fc.string({ minLength: 1, maxLength: 30 }), { minLength: 0, maxLength: 8 }), fc.integer({ min: 1, max: 10000 }), (fields, budget) => {
145
+ const manifest = mkManifest(fields.length ? fields : ['x'], budget, fields.length ? fields : ['x']);
146
+ const map = new Map(fields.map((f) => [f, f.repeat(10)]));
147
+ const c = capture();
148
+ const result = allocateContext(manifest, map, c.emit);
149
+ expect(result.usedTokens).toBeLessThanOrEqual(result.budgetTokens);
150
+ }), { numRuns: 80 });
151
+ });
152
+ it('every truncated/dropped field has a TruncationRecord AND a context_truncated event (rc-9, ERR-002)', () => {
153
+ // Force budget exhaustion: 5 fields each ~40 chars (10 tokens), budget=15.
154
+ const fields = ['f1', 'f2', 'f3', 'f4', 'f5'];
155
+ const manifest = mkManifest(fields, 15, fields);
156
+ const map = new Map(fields.map((f) => [f, 'x'.repeat(40)])); // 40 chars → 10 tokens each
157
+ const c = capture();
158
+ const result = allocateContext(manifest, map, c.emit);
159
+ // At least one field fit (1) + at most one partial, rest dropped.
160
+ expect(result.truncated.length + Object.keys(result.fields).length).toBeGreaterThan(0);
161
+ // Every truncated record has a matching event.
162
+ expect(c.events.length).toBe(result.truncated.length);
163
+ for (const rec of result.truncated) {
164
+ const matchingEvent = c.events.find((e) => e.fieldPath === rec.fieldPath);
165
+ expect(matchingEvent, `event for ${rec.fieldPath}`).toBeDefined();
166
+ expect(matchingEvent?.reason).toBe(rec.reason);
167
+ }
168
+ });
169
+ it('usedTokens === sum of token costs of fully-fit serialized fields (no partials)', () => {
170
+ // Token cost is computed on the SERIALIZED value (safeStringifyPreview adds
171
+ // JSON quotes around strings), so the expected sum must use the serialized form.
172
+ const fields = ['a', 'b'];
173
+ const manifest = mkManifest(fields, 1000, fields);
174
+ const map = new Map([
175
+ ['a', 'short'],
176
+ ['b', 'tiny'],
177
+ ]);
178
+ const c = capture();
179
+ const result = allocateContext(manifest, map, c.emit);
180
+ expect(c.events).toHaveLength(0); // nothing truncated
181
+ // safeStringifyPreview('short') = '"short"' (7 chars), ('tiny') = '"tiny"' (6 chars)
182
+ const expected = estimateTokens(JSON.stringify('short')) + estimateTokens(JSON.stringify('tiny'));
183
+ expect(result.usedTokens).toBe(expected);
184
+ });
185
+ it('budget scope: injecting extra non-manifest keys does not change usedTokens', () => {
186
+ // design §6.2.1: budget covers ONLY manifest-declared fields. Adding
187
+ // core-grounding-like or instruction-like keys to the map must not affect
188
+ // the budget accounting.
189
+ const manifest = mkManifest(['a.summary.x'], 1000, ['a.summary.x']);
190
+ const minimal = new Map([['a.summary.x', 'val']]);
191
+ const withExtras = new Map([
192
+ ['a.summary.x', 'val'],
193
+ ['core.grounding', 'G'.repeat(5000)], // not in manifest
194
+ ['runner.instructions', 'I'.repeat(5000)], // not in manifest
195
+ ]);
196
+ const c1 = capture();
197
+ const c2 = capture();
198
+ const r1 = allocateContext(manifest, minimal, c1.emit);
199
+ const r2 = allocateContext(manifest, withExtras, c2.emit);
200
+ expect(r2.usedTokens).toBe(r1.usedTokens);
201
+ expect(r2.budgetTokens).toBe(r1.budgetTokens);
202
+ });
203
+ });
204
+ // ── CP-16: bounded-safe preview serialization ────────────────────────────────
205
+ describe('CP-16 — bounded-safe preview serialization (rc-8, ERR-013)', () => {
206
+ it('adversarial values never throw and never exceed FIELD_PREVIEW_MAX_CHARS+marker', () => {
207
+ const cyclic = { a: 1 };
208
+ cyclic.self = cyclic;
209
+ const adversarial = [
210
+ cyclic,
211
+ BigInt(123),
212
+ Symbol('s'),
213
+ (() => 'fn'),
214
+ new Map([['k', 'v']]),
215
+ new Set([1, 2]),
216
+ new Date('2020-01-01'),
217
+ Number.NaN,
218
+ { __proto__: { polluted: true } },
219
+ { constructor: 'not-a-fn' },
220
+ { toString: 'not-a-fn' },
221
+ 'x'.repeat(10000),
222
+ null,
223
+ undefined,
224
+ ];
225
+ const manifest = mkManifest(['k'], 100000, ['k']);
226
+ for (const val of adversarial) {
227
+ const map = new Map([['k', val]]);
228
+ const c = capture();
229
+ expect(() => allocateContext(manifest, map, c.emit)).not.toThrow();
230
+ const result = allocateContext(manifest, map, c.emit);
231
+ const fieldText = result.fields.k;
232
+ if (fieldText !== undefined) {
233
+ // safeStringifyPreview truncates at FIELD_PREVIEW_MAX_CHARS (600); the
234
+ // budget layer may append a TRUNCATION_MARKER on partial truncate.
235
+ // Either way the stored text is bounded and finite.
236
+ expect(fieldText.length).toBeLessThanOrEqual(FIELD_PREVIEW_MAX_CHARS + 50); // marker slack
237
+ expect(Number.isFinite(fieldText.length)).toBe(true);
238
+ }
239
+ }
240
+ });
241
+ it('prototype-polluting keys do not leak into the allocated fields', () => {
242
+ // A value with a __proto__ key must not pollute Object when serialized.
243
+ const manifest = mkManifest(['k'], 1000, ['k']);
244
+ const malicious = JSON.parse('{"__proto__":{"polluted":"yes"},"normal":"val"}');
245
+ const map = new Map([['k', malicious]]);
246
+ const c = capture();
247
+ const result = allocateContext(manifest, map, c.emit);
248
+ // The field is serialized to a string (no prototype pollution possible
249
+ // from a string). Verify the process didn't pollute Object.prototype.
250
+ expect({}.polluted).toBeUndefined();
251
+ expect(Object.keys(result.fields)).toContain('k');
252
+ });
253
+ });
254
+ //# sourceMappingURL=prompt-budget-manager.property.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt-budget-manager.property.test.js","sourceRoot":"","sources":["../../../../src/runtime-v2/internalization/__tests__/prompt-budget-manager.property.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Property tests for resolveInjection information-floor fallback
3
+ * (design §6.2.2, task 5.11).
4
+ *
5
+ * CP-34: injection information floor — Layer 1 must not produce a thinner
6
+ * prompt than the flag-off baseline. When the manifest resolution is too
7
+ * sparse (empty / all tier1 absent / absent ratio > threshold), resolveInjection
8
+ * falls back to the legacy full-predecessor injection and emits exactly one
9
+ * manifest_resolution_insufficient event.
10
+ *
11
+ * @see .kiro/specs/internalization-progressive-disclosure/design.md §6.2.2
12
+ * @see .kiro/specs/internalization-progressive-disclosure/requirements.md Requirement 13
13
+ */
14
+ export {};
15
+ //# sourceMappingURL=resolve-injection.property.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-injection.property.test.d.ts","sourceRoot":"","sources":["../../../../src/runtime-v2/internalization/__tests__/resolve-injection.property.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Property tests for resolveInjection information-floor fallback
3
+ * (design §6.2.2, task 5.11).
4
+ *
5
+ * CP-34: injection information floor — Layer 1 must not produce a thinner
6
+ * prompt than the flag-off baseline. When the manifest resolution is too
7
+ * sparse (empty / all tier1 absent / absent ratio > threshold), resolveInjection
8
+ * falls back to the legacy full-predecessor injection and emits exactly one
9
+ * manifest_resolution_insufficient event.
10
+ *
11
+ * @see .kiro/specs/internalization-progressive-disclosure/design.md §6.2.2
12
+ * @see .kiro/specs/internalization-progressive-disclosure/requirements.md Requirement 13
13
+ */
14
+ import { describe, it, expect } from 'vitest';
15
+ import fc from 'fast-check';
16
+ import { resolveInjection, MANIFEST_ABSENT_RATIO_THRESHOLD, } from '../resolve-injection.js';
17
+ import { CONTEXT_MANIFEST_SCHEMA_VERSION } from '../context-manifest.js';
18
+ /** Build a manifest with N tier1 fields, all in priority. */
19
+ function mkManifest(tier1Fields, budget = 1000) {
20
+ return {
21
+ manifestId: 'test.v1',
22
+ schemaVersion: CONTEXT_MANIFEST_SCHEMA_VERSION,
23
+ runnerKind: 'dreamer',
24
+ tier0: [],
25
+ tier1: tier1Fields,
26
+ tier2: [],
27
+ budgetTokens: budget,
28
+ priority: tier1Fields,
29
+ };
30
+ }
31
+ function capture() {
32
+ const events = [];
33
+ return { emit: (e) => events.push(e), events };
34
+ }
35
+ function isFallback(e) {
36
+ return e.type === 'manifest_resolution_insufficient';
37
+ }
38
+ // ── CP-34: information floor ─────────────────────────────────────────────────
39
+ describe('CP-34 — resolveInjection information-floor fallback', () => {
40
+ it('threshold is 0.5 (documented constant)', () => {
41
+ expect(MANIFEST_ABSENT_RATIO_THRESHOLD).toBe(0.5);
42
+ });
43
+ it('focused when all fields present (absent ratio 0)', () => {
44
+ const m = mkManifest(['a', 'b', 'c', 'd']);
45
+ const map = new Map([['a', '1'], ['b', '2'], ['c', '3'], ['d', '4']]);
46
+ const c = capture();
47
+ const result = resolveInjection(m, map, c.emit);
48
+ expect(result.kind).toBe('focused');
49
+ expect(result.fellBack).toBe(false);
50
+ expect(c.events.filter(isFallback)).toHaveLength(0);
51
+ });
52
+ it('falls back when allocation is empty (no field resolved)', () => {
53
+ const m = mkManifest(['a', 'b']);
54
+ const map = new Map(); // nothing available
55
+ const c = capture();
56
+ const result = resolveInjection(m, map, c.emit);
57
+ expect(result.kind).toBe('fallback');
58
+ expect(result.fellBack).toBe(true);
59
+ const fb = c.events.filter(isFallback);
60
+ expect(fb).toHaveLength(1);
61
+ expect(fb[0]?.fallback).toBe('full_predecessor_injection');
62
+ expect(fb[0]?.absentCount).toBe(2);
63
+ expect(fb[0]?.declaredCount).toBe(2);
64
+ });
65
+ it('falls back when all tier1 fields are absent', () => {
66
+ const m = mkManifest(['a', 'b', 'c']); // all tier1
67
+ const map = new Map(); // all absent
68
+ const c = capture();
69
+ const result = resolveInjection(m, map, c.emit);
70
+ expect(result.kind).toBe('fallback');
71
+ // empty-allocation trigger fires first (all absent → nothing allocated)
72
+ if (result.kind === 'fallback') {
73
+ expect(['empty_allocation', 'tier1_all_absent']).toContain(result.reason);
74
+ }
75
+ });
76
+ it('falls back when absent ratio > threshold (0.5)', () => {
77
+ // 4 declared, 3 absent → ratio 0.75 > 0.5. The 1 present field is in tier1
78
+ // (so tier1_all_absent is false), forcing the ratio trigger.
79
+ const m = mkManifest(['a', 'b', 'c', 'd']);
80
+ const map = new Map([['a', '1']]); // only 'a' present
81
+ const c = capture();
82
+ const result = resolveInjection(m, map, c.emit);
83
+ expect(result.kind).toBe('fallback');
84
+ if (result.kind === 'fallback') {
85
+ expect(result.absentRatio).toBe(0.75);
86
+ }
87
+ });
88
+ it('stays focused at exactly the threshold boundary (ratio == 0.5, not >)', () => {
89
+ // 4 declared, 2 absent → ratio exactly 0.5. Trigger is `> threshold`, so
90
+ // this must NOT fall back (boundary is inclusive of focused).
91
+ const m = mkManifest(['a', 'b', 'c', 'd']);
92
+ const map = new Map([['a', '1'], ['b', '2']]);
93
+ const c = capture();
94
+ const result = resolveInjection(m, map, c.emit);
95
+ expect(result.kind).toBe('focused');
96
+ expect(c.events.filter(isFallback)).toHaveLength(0);
97
+ });
98
+ it('property: fallback fires iff absentRatio > 0.5 OR allocation empty OR tier1 all absent', () => {
99
+ // Generate a 4-field manifest, randomly present each field, and verify the
100
+ // fallback decision matches the documented rule exactly.
101
+ fc.assert(fc.property(fc.array(fc.boolean(), { minLength: 4, maxLength: 4 }), (presentMask) => {
102
+ const m = mkManifest(['a', 'b', 'c', 'd']);
103
+ const map = new Map();
104
+ const fields = ['a', 'b', 'c', 'd'];
105
+ presentMask.forEach((present, i) => {
106
+ const f = fields[i];
107
+ if (present && f !== undefined)
108
+ map.set(f, `v${i}`);
109
+ });
110
+ const c = capture();
111
+ const result = resolveInjection(m, map, c.emit);
112
+ const presentCount = presentMask.filter(Boolean).length;
113
+ const absentCount = 4 - presentCount;
114
+ const absentRatio = absentCount / 4;
115
+ const shouldFallback = presentCount === 0 // empty allocation
116
+ || absentRatio > MANIFEST_ABSENT_RATIO_THRESHOLD; // tier1_all_absent coincides with all-absent here
117
+ expect(result.fellBack).toBe(shouldFallback);
118
+ // Exactly one fallback event when fallback, zero otherwise.
119
+ const fbCount = c.events.filter(isFallback).length;
120
+ expect(fbCount).toBe(shouldFallback ? 1 : 0);
121
+ }), { numRuns: 50 });
122
+ });
123
+ it('fallback event carries absentCount, declaredCount, absentRatio, fallback (rc-9)', () => {
124
+ const m = mkManifest(['a', 'b', 'c', 'd']);
125
+ const map = new Map([['a', '1']]); // 3 absent
126
+ const c = capture();
127
+ resolveInjection(m, map, c.emit);
128
+ const fb = c.events.filter(isFallback);
129
+ expect(fb).toHaveLength(1);
130
+ expect(fb[0]?.absentCount).toBe(3);
131
+ expect(fb[0]?.declaredCount).toBe(4);
132
+ expect(fb[0]?.absentRatio).toBeCloseTo(0.75, 5);
133
+ expect(fb[0]?.fallback).toBe('full_predecessor_injection');
134
+ });
135
+ it('context_truncated events from the allocation attempt are preserved on fallback', () => {
136
+ // When fallback fires, the allocateContext that ran first may have emitted
137
+ // context_truncated events; those must still be in the event stream (design
138
+ // §6.3: they record "what would have been truncated").
139
+ const m = mkManifest(['a', 'b'], 1); // budget=1 forces truncation
140
+ const map = new Map([['a', 'x'.repeat(100)], ['b', 'y'.repeat(100)]]);
141
+ const c = capture();
142
+ const result = resolveInjection(m, map, c.emit);
143
+ // Budget too small → both fields likely dropped → empty allocation → fallback.
144
+ expect(result.kind).toBe('fallback');
145
+ const truncated = c.events.filter((e) => e.type === 'context_truncated');
146
+ // At least the context_truncated events from the attempt are present.
147
+ expect(truncated.length).toBeGreaterThan(0);
148
+ });
149
+ });
150
+ //# sourceMappingURL=resolve-injection.property.test.js.map