@principles/core 1.240.21 → 1.241.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__/artifact-summary-redundancy-flag.test.d.ts +15 -0
- package/dist/runtime-v2/feature-flags/__tests__/artifact-summary-redundancy-flag.test.d.ts.map +1 -0
- package/dist/runtime-v2/feature-flags/__tests__/artifact-summary-redundancy-flag.test.js +90 -0
- package/dist/runtime-v2/feature-flags/__tests__/artifact-summary-redundancy-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 +6 -0
- package/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
- package/dist/runtime-v2/internalization/__tests__/artifact-content-hash.property.test.d.ts +11 -0
- package/dist/runtime-v2/internalization/__tests__/artifact-content-hash.property.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/artifact-content-hash.property.test.js +124 -0
- package/dist/runtime-v2/internalization/__tests__/artifact-content-hash.property.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/artifact-summary.property.test.d.ts +12 -0
- package/dist/runtime-v2/internalization/__tests__/artifact-summary.property.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/artifact-summary.property.test.js +253 -0
- package/dist/runtime-v2/internalization/__tests__/artifact-summary.property.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/philosopher-toggle-characterization.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/philosopher-toggle-characterization.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/philosopher-toggle-characterization.test.js +279 -0
- package/dist/runtime-v2/internalization/__tests__/philosopher-toggle-characterization.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-scope-regression.test.d.ts +22 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-scope-regression.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-scope-regression.test.js +92 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-scope-regression.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike-fixtures.d.ts +165 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike-fixtures.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike-fixtures.js +657 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike-fixtures.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike-fixtures.test.d.ts +22 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike-fixtures.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike-fixtures.test.js +229 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike-fixtures.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike.test.d.ts +2 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike.test.js +1039 -0
- package/dist/runtime-v2/internalization/__tests__/progressive-disclosure-spike.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/summary-chain-flow.property.test.d.ts +22 -0
- package/dist/runtime-v2/internalization/__tests__/summary-chain-flow.property.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/summary-chain-flow.property.test.js +280 -0
- package/dist/runtime-v2/internalization/__tests__/summary-chain-flow.property.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/summary-envelope-writer.property.test.d.ts +25 -0
- package/dist/runtime-v2/internalization/__tests__/summary-envelope-writer.property.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/summary-envelope-writer.property.test.js +413 -0
- package/dist/runtime-v2/internalization/__tests__/summary-envelope-writer.property.test.js.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/summary-freshness.property.test.d.ts +12 -0
- package/dist/runtime-v2/internalization/__tests__/summary-freshness.property.test.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/__tests__/summary-freshness.property.test.js +176 -0
- package/dist/runtime-v2/internalization/__tests__/summary-freshness.property.test.js.map +1 -0
- package/dist/runtime-v2/internalization/artifact-content-hash.d.ts +19 -0
- package/dist/runtime-v2/internalization/artifact-content-hash.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/artifact-content-hash.js +116 -0
- package/dist/runtime-v2/internalization/artifact-content-hash.js.map +1 -0
- package/dist/runtime-v2/internalization/artifact-summary.d.ts +106 -0
- package/dist/runtime-v2/internalization/artifact-summary.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/artifact-summary.js +331 -0
- package/dist/runtime-v2/internalization/artifact-summary.js.map +1 -0
- package/dist/runtime-v2/internalization/artificer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/artificer-runner.js +29 -1
- package/dist/runtime-v2/internalization/artificer-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/attach-summary-envelope.d.ts +60 -0
- package/dist/runtime-v2/internalization/attach-summary-envelope.d.ts.map +1 -0
- package/dist/runtime-v2/internalization/attach-summary-envelope.js +126 -0
- package/dist/runtime-v2/internalization/attach-summary-envelope.js.map +1 -0
- package/dist/runtime-v2/internalization/diag-distiller-runner.d.ts +1 -1
- package/dist/runtime-v2/internalization/diag-distiller-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/diag-distiller-runner.js +19 -2
- package/dist/runtime-v2/internalization/diag-distiller-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/diag-rootcause-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/diag-rootcause-runner.js +5 -1
- package/dist/runtime-v2/internalization/diag-rootcause-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/diag-router-runner.d.ts +1 -1
- package/dist/runtime-v2/internalization/diag-router-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/diag-router-runner.js +25 -1
- package/dist/runtime-v2/internalization/diag-router-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/dreamer-runner.d.ts +9 -1
- package/dist/runtime-v2/internalization/dreamer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/dreamer-runner.js +28 -6
- package/dist/runtime-v2/internalization/dreamer-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/evaluator-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/evaluator-runner.js +35 -2
- package/dist/runtime-v2/internalization/evaluator-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/philosopher-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/philosopher-runner.js +25 -1
- package/dist/runtime-v2/internalization/philosopher-runner.js.map +1 -1
- package/dist/runtime-v2/internalization/scribe-runner.d.ts.map +1 -1
- package/dist/runtime-v2/internalization/scribe-runner.js +23 -1
- package/dist/runtime-v2/internalization/scribe-runner.js.map +1 -1
- package/dist/runtime-v2/runner/base-peer-runner.d.ts +30 -0
- package/dist/runtime-v2/runner/base-peer-runner.d.ts.map +1 -1
- package/dist/runtime-v2/runner/base-peer-runner.js +106 -0
- package/dist/runtime-v2/runner/base-peer-runner.js.map +1 -1
- package/dist/runtime-v2/runner/peer-runner-types.d.ts +12 -0
- package/dist/runtime-v2/runner/peer-runner-types.d.ts.map +1 -1
- package/dist/runtime-v2/store/commit/diagnostician-committer.d.ts +15 -0
- package/dist/runtime-v2/store/commit/diagnostician-committer.d.ts.map +1 -1
- package/dist/runtime-v2/store/commit/diagnostician-committer.js +19 -1
- package/dist/runtime-v2/store/commit/diagnostician-committer.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer 0 writer-side property tests (design §6.1, tasks 3.12–3.20).
|
|
3
|
+
*
|
|
4
|
+
* These exercise the *wiring* layer (`BasePeerRunner.buildArtifactContentJson`,
|
|
5
|
+
* which calls `attachSummaryEnvelope`), not the pure derivation functions
|
|
6
|
+
* (those are covered by `artifact-summary.property.test.ts`). The wiring layer
|
|
7
|
+
* is where the feature flag, the predecessor-contentHash injection, the
|
|
8
|
+
* degradation telemetry, and the "summary failure must not affect task success"
|
|
9
|
+
* contract (F11) all live.
|
|
10
|
+
*
|
|
11
|
+
* CP-04: derivation failure does not affect task success / failure
|
|
12
|
+
* CP-05: predecessor summary adds zero extra store reads & is same-source
|
|
13
|
+
* CP-06: envelope depth is always exactly one (no nested predecessorSummary)
|
|
14
|
+
* CP-11: stale summary content never enters the written contentJson
|
|
15
|
+
* CP-32: contentJson key-level compatibility (flag on vs off, byte-identical
|
|
16
|
+
* aside from summary/predecessorSummary)
|
|
17
|
+
* CP-35: direct-predecessor uniqueness (evaluator loads artificer+scribe,
|
|
18
|
+
* diag_router loads rootcause+distiller — only the edge predecessor
|
|
19
|
+
* lands in predecessorSummary)
|
|
20
|
+
*
|
|
21
|
+
* @see .kiro/specs/internalization-progressive-disclosure/design.md §6.1, §16
|
|
22
|
+
* @see .kiro/specs/internalization-progressive-disclosure/requirements.md Requirements 1.8, 2, 3.6, 11.5–11.9
|
|
23
|
+
*/
|
|
24
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
25
|
+
import { createHash } from 'node:crypto';
|
|
26
|
+
import fc from 'fast-check';
|
|
27
|
+
import { BasePeerRunner } from '../../runner/base-peer-runner.js';
|
|
28
|
+
import { getDefaultPdConfig } from '../../config/pd-config-defaults.js';
|
|
29
|
+
import { computeEffectivePdConfig } from '../../config/pd-config-effective.js';
|
|
30
|
+
import { computeContentHash } from '../artifact-content-hash.js';
|
|
31
|
+
const sha256 = (input) => createHash('sha256').update(input).digest('hex');
|
|
32
|
+
const TASK_ID = 'task-layer0-writer-001';
|
|
33
|
+
/**
|
|
34
|
+
* Build an EffectivePdConfig with the Layer 0 flag on or off. Uses the real
|
|
35
|
+
* production config-merge path (`computeEffectivePdConfig`) rather than
|
|
36
|
+
* hand-rolling the EffectivePdConfig shape — this is the same path the
|
|
37
|
+
* production loader uses (ERR-024: test the real wiring).
|
|
38
|
+
*/
|
|
39
|
+
function makeEffectiveConfig(opts) {
|
|
40
|
+
if (!opts.summaryEnabled) {
|
|
41
|
+
return computeEffectivePdConfig(null);
|
|
42
|
+
}
|
|
43
|
+
const base = getDefaultPdConfig();
|
|
44
|
+
const userConfig = {
|
|
45
|
+
...base,
|
|
46
|
+
features: {
|
|
47
|
+
...base.features,
|
|
48
|
+
artifact_summary_redundancy: { category: 'quiet', enabled: true },
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
return computeEffectivePdConfig(userConfig);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Test runner exposing the protected `buildArtifactContentJson` directly.
|
|
55
|
+
* Mirrors the DegradationTestRunner pattern from
|
|
56
|
+
* base-peer-runner-rate-limit-degradation.test.ts.
|
|
57
|
+
*/
|
|
58
|
+
class Layer0TestRunner extends BasePeerRunner {
|
|
59
|
+
constructor(deps, effectiveConfig) {
|
|
60
|
+
super(deps, { owner: 'test', runtimeKind: 'test-double' }, {
|
|
61
|
+
runnerName: 'test',
|
|
62
|
+
expectedTaskKind: 'dreamer',
|
|
63
|
+
defaultAgentId: 'test',
|
|
64
|
+
resultRefPrefix: 'test',
|
|
65
|
+
effectiveConfig,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
69
|
+
get permanentErrorCategories() {
|
|
70
|
+
return new Set(['input_invalid', 'output_invalid']);
|
|
71
|
+
}
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
73
|
+
async buildContext() {
|
|
74
|
+
return { contextHash: 'test-hash' };
|
|
75
|
+
}
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
77
|
+
async invokeRuntime() {
|
|
78
|
+
return { runId: 'run-001', runtimeKind: 'test-double', startedAt: new Date().toISOString() };
|
|
79
|
+
}
|
|
80
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this
|
|
81
|
+
async validateOutput() {
|
|
82
|
+
return { valid: true, errors: [] };
|
|
83
|
+
}
|
|
84
|
+
// eslint-disable-next-line @typescript-eslint/class-methods-use-this, @typescript-eslint/max-params
|
|
85
|
+
async succeedTask(taskId, runId, _output, task) {
|
|
86
|
+
return { status: 'succeeded', taskId, runId, attemptCount: task.attemptCount };
|
|
87
|
+
}
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/max-params -- mirrors the protected method under test 1:1.
|
|
89
|
+
callBuildArtifactContentJson(taskId, runnerKind, output, loadedPredecessor) {
|
|
90
|
+
return this.buildArtifactContentJson(taskId, runnerKind, output, loadedPredecessor);
|
|
91
|
+
}
|
|
92
|
+
/** Expose the flag check for direct assertion. */
|
|
93
|
+
callIsArtifactSummaryEnabled() {
|
|
94
|
+
return this.isArtifactSummaryEnabled();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function createMockDeps(overrides) {
|
|
98
|
+
return {
|
|
99
|
+
stateManager: {
|
|
100
|
+
getRetryPolicy: vi.fn().mockReturnValue({ shouldRetry: vi.fn().mockReturnValue(false) }),
|
|
101
|
+
markTaskFailed: vi.fn().mockResolvedValue({}),
|
|
102
|
+
markTaskSucceeded: vi.fn().mockResolvedValue({}),
|
|
103
|
+
updateRunOutput: vi.fn().mockResolvedValue({}),
|
|
104
|
+
},
|
|
105
|
+
runtimeAdapter: {},
|
|
106
|
+
eventEmitter: {
|
|
107
|
+
emitTelemetry: vi.fn(),
|
|
108
|
+
},
|
|
109
|
+
artifactStore: {
|
|
110
|
+
// Counting stub: CP-05 proves zero extra reads. Every method the writer
|
|
111
|
+
// path could reach is a spy that throws if called unexpectedly.
|
|
112
|
+
getArtifactById: vi.fn(),
|
|
113
|
+
listBySourceTaskId: vi.fn(),
|
|
114
|
+
},
|
|
115
|
+
contentHashFn: sha256,
|
|
116
|
+
...overrides,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function telemetryCalls(mock) {
|
|
120
|
+
return mock.mock.calls.map((call) => call[0]);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* `emitEvent` prefixes eventType with the runner name (e.g. `test_...`).
|
|
124
|
+
* This helper matches on the suffix so tests stay runner-name agnostic.
|
|
125
|
+
*/
|
|
126
|
+
function telemetryWithSuffix(mock, suffix) {
|
|
127
|
+
return telemetryCalls(mock).filter((c) => c.eventType.endsWith(`_${suffix}`));
|
|
128
|
+
}
|
|
129
|
+
/** A legal dreamer-shaped output for the wiring tests. */
|
|
130
|
+
const DREAMER_OUTPUT = {
|
|
131
|
+
candidates: [{
|
|
132
|
+
badDecision: 'renamed without checking callers',
|
|
133
|
+
betterDecision: 'audit file tree and grep imports before rename',
|
|
134
|
+
rationale: 'cross-package callers break compilation',
|
|
135
|
+
riskLevel: 'high',
|
|
136
|
+
strategicPerspective: 'caller-side lens',
|
|
137
|
+
}],
|
|
138
|
+
};
|
|
139
|
+
function makePredecessor(runnerKind, content) {
|
|
140
|
+
return {
|
|
141
|
+
artifactId: `pred-${runnerKind}`,
|
|
142
|
+
runnerKind,
|
|
143
|
+
contentJson: content,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
// ── CP-32: contentJson key-level compatibility ───────────────────────────────
|
|
147
|
+
describe('Layer 0 — CP-32 contentJson key-level compatibility', () => {
|
|
148
|
+
it('flag OFF → contentJson is byte-identical to JSON.stringify(output)', () => {
|
|
149
|
+
const deps = createMockDeps();
|
|
150
|
+
const runner = new Layer0TestRunner(deps); // no effectiveConfig → flag off
|
|
151
|
+
expect(runner.callIsArtifactSummaryEnabled()).toBe(false);
|
|
152
|
+
const result = runner.callBuildArtifactContentJson(TASK_ID, 'dreamer', DREAMER_OUTPUT, null);
|
|
153
|
+
expect(result).toBe(JSON.stringify(DREAMER_OUTPUT));
|
|
154
|
+
// And the same when a predecessor is provided: flag off = no envelope at all.
|
|
155
|
+
const resultWithPred = runner.callBuildArtifactContentJson(TASK_ID, 'dreamer', DREAMER_OUTPUT, makePredecessor('diag_router', { summary: 'router output' }));
|
|
156
|
+
expect(resultWithPred).toBe(JSON.stringify(DREAMER_OUTPUT));
|
|
157
|
+
});
|
|
158
|
+
it('flag ON → every original output key is preserved (only summary/predecessorSummary added)', () => {
|
|
159
|
+
const deps = createMockDeps();
|
|
160
|
+
const runner = new Layer0TestRunner(deps, makeEffectiveConfig({ summaryEnabled: true }));
|
|
161
|
+
expect(runner.callIsArtifactSummaryEnabled()).toBe(true);
|
|
162
|
+
const result = runner.callBuildArtifactContentJson(TASK_ID, 'dreamer', DREAMER_OUTPUT, makePredecessor('diag_router', { summary: 'router output' }));
|
|
163
|
+
const parsed = JSON.parse(result);
|
|
164
|
+
// Every original key from DREAMER_OUTPUT is present and unchanged.
|
|
165
|
+
const original = DREAMER_OUTPUT;
|
|
166
|
+
for (const key of Object.keys(original)) {
|
|
167
|
+
expect(parsed[key]).toEqual(original[key]);
|
|
168
|
+
}
|
|
169
|
+
// The two additive envelope keys are present.
|
|
170
|
+
expect(Object.hasOwn(parsed, 'summary')).toBe(true);
|
|
171
|
+
expect(Object.hasOwn(parsed, 'predecessorSummary')).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
it('flag ON with no predecessor → only `summary` added, no `predecessorSummary` key', () => {
|
|
174
|
+
const deps = createMockDeps();
|
|
175
|
+
const runner = new Layer0TestRunner(deps, makeEffectiveConfig({ summaryEnabled: true }));
|
|
176
|
+
const result = runner.callBuildArtifactContentJson(TASK_ID, 'dreamer', DREAMER_OUTPUT, null);
|
|
177
|
+
const parsed = JSON.parse(result);
|
|
178
|
+
expect(Object.hasOwn(parsed, 'summary')).toBe(true);
|
|
179
|
+
expect(Object.hasOwn(parsed, 'predecessorSummary')).toBe(false);
|
|
180
|
+
});
|
|
181
|
+
it('P1 regression: output already declaring a top-level `summary` field (diag_rootcause / diag_router) is NOT overwritten', () => {
|
|
182
|
+
// CodeRabbit PR #1273: DiagRootCauseOutputV1 and DiagnosticianOutputV1 both
|
|
183
|
+
// declare a top-level `summary` string field. The Layer 0 envelope must not
|
|
184
|
+
// clobber it. On collision, the envelope `summary` key is skipped, a
|
|
185
|
+
// structured degradation is emitted, and predecessorSummary is still
|
|
186
|
+
// attached (it does not collide).
|
|
187
|
+
const deps = createMockDeps();
|
|
188
|
+
const runner = new Layer0TestRunner(deps, makeEffectiveConfig({ summaryEnabled: true }));
|
|
189
|
+
const emitMock = deps.eventEmitter.emitTelemetry;
|
|
190
|
+
// diag_router-shaped output carries a legitimate top-level `summary` field.
|
|
191
|
+
const routerOutput = {
|
|
192
|
+
summary: 'route to internalization: principle guard', // legitimate output field
|
|
193
|
+
rootCause: 'rename without enumeration',
|
|
194
|
+
violatedPrinciples: [],
|
|
195
|
+
recommendations: [{ kind: 'principle', description: 'guard', triggerPattern: 'x', action: 'y' }],
|
|
196
|
+
};
|
|
197
|
+
const result = runner.callBuildArtifactContentJson(TASK_ID, 'diag_router', routerOutput, makePredecessor('diag_distiller', { abstractedPrinciple: 'p', rationale: 'r', scope: 'general' }));
|
|
198
|
+
const parsed = JSON.parse(result);
|
|
199
|
+
// The legitimate output.summary is preserved verbatim (NOT overwritten by
|
|
200
|
+
// the ArtifactSummary envelope object).
|
|
201
|
+
expect(parsed.summary).toBe('route to internalization: principle guard');
|
|
202
|
+
expect(typeof parsed.summary).toBe('string');
|
|
203
|
+
// predecessorSummary is still attached (no collision).
|
|
204
|
+
expect(Object.hasOwn(parsed, 'predecessorSummary')).toBe(true);
|
|
205
|
+
expect(parsed.predecessorSummary.runnerKind).toBe('diag_distiller');
|
|
206
|
+
// A structured degradation was emitted for the collision (rc-9).
|
|
207
|
+
const skipped = telemetryWithSuffix(emitMock, 'artifact_summary_skipped');
|
|
208
|
+
expect(skipped.length).toBe(1);
|
|
209
|
+
expect(skipped[0]?.payload?.reason).toBe('output_summary_key_collision');
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
// ── CP-04: derivation failure does not affect task success ───────────────────
|
|
213
|
+
describe('Layer 0 — CP-04 derivation failure does not affect task success', () => {
|
|
214
|
+
it('returns a valid contentJson (no throw) and emits artifact_summary_skipped when output has no derivable field', () => {
|
|
215
|
+
const deps = createMockDeps();
|
|
216
|
+
const runner = new Layer0TestRunner(deps, makeEffectiveConfig({ summaryEnabled: true }));
|
|
217
|
+
const emitMock = deps.eventEmitter.emitTelemetry;
|
|
218
|
+
// An object with none of the fields any resolver looks for → no_derivable_field.
|
|
219
|
+
const bare = { unrelated: 'value' };
|
|
220
|
+
const result = runner.callBuildArtifactContentJson(TASK_ID, 'dreamer', bare, null);
|
|
221
|
+
// No throw, valid JSON, and falls back to the bare output (no summary key).
|
|
222
|
+
expect(() => JSON.parse(result)).not.toThrow();
|
|
223
|
+
const parsed = JSON.parse(result);
|
|
224
|
+
expect(Object.hasOwn(parsed, 'summary')).toBe(false);
|
|
225
|
+
// Exactly one artifact_summary_skipped degradation event emitted (rc-9).
|
|
226
|
+
const skipped = telemetryWithSuffix(emitMock, 'artifact_summary_skipped');
|
|
227
|
+
expect(skipped.length).toBe(1);
|
|
228
|
+
expect(skipped[0]?.payload?.reason).toBe('no_derivable_field');
|
|
229
|
+
});
|
|
230
|
+
it('output_not_object (e.g. a string) → no summary, no throw', () => {
|
|
231
|
+
const deps = createMockDeps();
|
|
232
|
+
const runner = new Layer0TestRunner(deps, makeEffectiveConfig({ summaryEnabled: true }));
|
|
233
|
+
const result = runner.callBuildArtifactContentJson(TASK_ID, 'dreamer', 'not-an-object', null);
|
|
234
|
+
// Falls back to JSON.stringify of the input; no summary.
|
|
235
|
+
expect(result).toBe(JSON.stringify('not-an-object'));
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
// ── CP-06: envelope depth is always exactly one ──────────────────────────────
|
|
239
|
+
describe('Layer 0 — CP-06 envelope depth is always exactly one', () => {
|
|
240
|
+
it('predecessorSummary never carries its own predecessorSummary key', () => {
|
|
241
|
+
const deps = createMockDeps();
|
|
242
|
+
const runner = new Layer0TestRunner(deps, makeEffectiveConfig({ summaryEnabled: true }));
|
|
243
|
+
// Even when the predecessor's own contentJson already contains a nested
|
|
244
|
+
// predecessorSummary (as if it were itself a mid-chain node), the freshly
|
|
245
|
+
// attached predecessorSummary must NOT propagate that nested ref — depth is
|
|
246
|
+
// capped at one (design §4.6 修正六).
|
|
247
|
+
const pred = makePredecessor('diag_router', {
|
|
248
|
+
summary: 'router output',
|
|
249
|
+
predecessorSummary: { artifactId: 'should-not-propagate', runnerKind: 'diag_distiller' },
|
|
250
|
+
});
|
|
251
|
+
const result = runner.callBuildArtifactContentJson(TASK_ID, 'dreamer', DREAMER_OUTPUT, pred);
|
|
252
|
+
const parsed = JSON.parse(result);
|
|
253
|
+
const predSummary = parsed.predecessorSummary;
|
|
254
|
+
expect(predSummary).toBeDefined();
|
|
255
|
+
// The only keys on predecessorSummary are artifactId/runnerKind/contentHash/summary.
|
|
256
|
+
const allowed = new Set(['artifactId', 'runnerKind', 'contentHash', 'summary']);
|
|
257
|
+
for (const key of Object.keys(predSummary)) {
|
|
258
|
+
expect(allowed.has(key)).toBe(true);
|
|
259
|
+
}
|
|
260
|
+
// And critically: no nested predecessorSummary.predecessorSummary.
|
|
261
|
+
const nested = predSummary.summary;
|
|
262
|
+
expect(Object.hasOwn(nested, 'predecessorSummary')).toBe(false);
|
|
263
|
+
});
|
|
264
|
+
it('a synthetic 1..6-hop chain never produces nested predecessorSummary at any hop', () => {
|
|
265
|
+
fc.assert(fc.property(fc.integer({ min: 1, max: 6 }), (hops) => {
|
|
266
|
+
const deps = createMockDeps();
|
|
267
|
+
const runner = new Layer0TestRunner(deps, makeEffectiveConfig({ summaryEnabled: true }));
|
|
268
|
+
// Build hops sequentially, feeding each hop's written contentJson as
|
|
269
|
+
// the next hop's predecessor content.
|
|
270
|
+
const kinds = ['diag_rootcause', 'diag_distiller', 'diag_router', 'dreamer', 'philosopher', 'scribe'];
|
|
271
|
+
let prevContent = { rootCause: 'seed root cause' };
|
|
272
|
+
let prevKind = 'diag_rootcause';
|
|
273
|
+
for (let i = 0; i < hops; i++) {
|
|
274
|
+
const kind = kinds[Math.min(i, kinds.length - 1)] ?? 'scribe';
|
|
275
|
+
const predecessor = i === 0
|
|
276
|
+
? null
|
|
277
|
+
: { artifactId: `art-${i}`, runnerKind: prevKind, contentJson: prevContent };
|
|
278
|
+
const out = { candidates: [{ betterDecision: 'hop action', riskLevel: 'low' }] };
|
|
279
|
+
const written = runner.callBuildArtifactContentJson(`task-${i}`, kind, out, predecessor);
|
|
280
|
+
const parsed = JSON.parse(written);
|
|
281
|
+
// Check this hop's predecessorSummary (if present) has no nesting.
|
|
282
|
+
const pred = parsed.predecessorSummary;
|
|
283
|
+
if (pred) {
|
|
284
|
+
const allowed = new Set(['artifactId', 'runnerKind', 'contentHash', 'summary']);
|
|
285
|
+
for (const key of Object.keys(pred))
|
|
286
|
+
expect(allowed.has(key)).toBe(true);
|
|
287
|
+
const inner = pred.summary;
|
|
288
|
+
expect(Object.hasOwn(inner, 'predecessorSummary')).toBe(false);
|
|
289
|
+
}
|
|
290
|
+
prevContent = parsed;
|
|
291
|
+
prevKind = kind;
|
|
292
|
+
}
|
|
293
|
+
}), { numRuns: 12 });
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
// ── CP-05: predecessor summary adds zero extra store reads & is same-source ──
|
|
297
|
+
describe('Layer 0 — CP-05 predecessor summary zero extra reads & same-source', () => {
|
|
298
|
+
it('flag ON with predecessor → no artifactStore.getArtifactById/listBySourceTaskId calls', () => {
|
|
299
|
+
const deps = createMockDeps();
|
|
300
|
+
const runner = new Layer0TestRunner(deps, makeEffectiveConfig({ summaryEnabled: true }));
|
|
301
|
+
const getMock = deps.artifactStore.getArtifactById;
|
|
302
|
+
const listMock = deps.artifactStore.listBySourceTaskId;
|
|
303
|
+
runner.callBuildArtifactContentJson(TASK_ID, 'dreamer', DREAMER_OUTPUT, makePredecessor('diag_router', { summary: 'router output' }));
|
|
304
|
+
expect(getMock).not.toHaveBeenCalled();
|
|
305
|
+
expect(listMock).not.toHaveBeenCalled();
|
|
306
|
+
});
|
|
307
|
+
it('predecessorSummary artifactId / runnerKind / contentHash are all same-source (rc-6)', () => {
|
|
308
|
+
const deps = createMockDeps();
|
|
309
|
+
const runner = new Layer0TestRunner(deps, makeEffectiveConfig({ summaryEnabled: true }));
|
|
310
|
+
const predContent = { summary: 'router output', rootCause: 'rc' };
|
|
311
|
+
const pred = {
|
|
312
|
+
artifactId: 'pred-art-999',
|
|
313
|
+
runnerKind: 'diag_router',
|
|
314
|
+
contentJson: predContent,
|
|
315
|
+
};
|
|
316
|
+
const result = runner.callBuildArtifactContentJson(TASK_ID, 'dreamer', DREAMER_OUTPUT, pred);
|
|
317
|
+
const parsed = JSON.parse(result);
|
|
318
|
+
const predSummary = parsed.predecessorSummary;
|
|
319
|
+
// All three fields come from the same predecessor object.
|
|
320
|
+
expect(predSummary.artifactId).toBe('pred-art-999');
|
|
321
|
+
expect(predSummary.runnerKind).toBe('diag_router');
|
|
322
|
+
// contentHash is the canonical (key-sorted) serialization of the predecessor content.
|
|
323
|
+
expect(predSummary.contentHash).toBe(computeContentHash(predContent, sha256));
|
|
324
|
+
});
|
|
325
|
+
it('null predecessor → artifact_summary_predecessor_absent emitted, self summary still written', () => {
|
|
326
|
+
const deps = createMockDeps();
|
|
327
|
+
const runner = new Layer0TestRunner(deps, makeEffectiveConfig({ summaryEnabled: true }));
|
|
328
|
+
const emitMock = deps.eventEmitter.emitTelemetry;
|
|
329
|
+
const result = runner.callBuildArtifactContentJson(TASK_ID, 'dreamer', DREAMER_OUTPUT, null);
|
|
330
|
+
const parsed = JSON.parse(result);
|
|
331
|
+
expect(Object.hasOwn(parsed, 'summary')).toBe(true);
|
|
332
|
+
expect(Object.hasOwn(parsed, 'predecessorSummary')).toBe(false);
|
|
333
|
+
const absent = telemetryWithSuffix(emitMock, 'artifact_summary_predecessor_absent');
|
|
334
|
+
expect(absent.length).toBe(1);
|
|
335
|
+
expect(absent[0]?.payload?.reason).toBe('no_predecessor_in_context');
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
// ── CP-11: stale summary content never enters the written contentJson ────────
|
|
339
|
+
//
|
|
340
|
+
// Note: CP-11's core invariant (stale predecessorSummary content is excluded
|
|
341
|
+
// from the injected context) is enforced by the READ-side freshness check
|
|
342
|
+
// (`checkPredecessorSummaryFreshness`, covered by summary-freshness.property
|
|
343
|
+
// .test.ts CP-09). The WRITE side tested here guarantees the contentHash is
|
|
344
|
+
// computed correctly so the read side can detect staleness. This test verifies
|
|
345
|
+
// the contentHash is stable for a given content, so a content change is always
|
|
346
|
+
// detectable.
|
|
347
|
+
describe('Layer 0 — CP-11 stale summary detectability (contentHash stability on write)', () => {
|
|
348
|
+
it('contentHash changes iff predecessor content changes (so stale refs are always detectable)', () => {
|
|
349
|
+
const deps = createMockDeps();
|
|
350
|
+
const runner = new Layer0TestRunner(deps, makeEffectiveConfig({ summaryEnabled: true }));
|
|
351
|
+
const contentA = { summary: 'router output v1' };
|
|
352
|
+
const contentB = { summary: 'router output v2' };
|
|
353
|
+
const r1 = runner.callBuildArtifactContentJson(TASK_ID, 'dreamer', DREAMER_OUTPUT, makePredecessor('diag_router', contentA));
|
|
354
|
+
const r2 = runner.callBuildArtifactContentJson(TASK_ID, 'dreamer', DREAMER_OUTPUT, makePredecessor('diag_router', contentA));
|
|
355
|
+
const r3 = runner.callBuildArtifactContentJson(TASK_ID, 'dreamer', DREAMER_OUTPUT, makePredecessor('diag_router', contentB));
|
|
356
|
+
const hashOf = (s) => {
|
|
357
|
+
const parsed = JSON.parse(s);
|
|
358
|
+
const pred = parsed.predecessorSummary;
|
|
359
|
+
return pred.contentHash;
|
|
360
|
+
};
|
|
361
|
+
// Same content → same hash (deterministic).
|
|
362
|
+
expect(hashOf(r1)).toBe(hashOf(r2));
|
|
363
|
+
// Different content → different hash (staleness is detectable).
|
|
364
|
+
expect(hashOf(r1)).not.toBe(hashOf(r3));
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
// ── CP-35: direct-predecessor uniqueness ──────────────────────────────────────
|
|
368
|
+
//
|
|
369
|
+
// F17: some runners load TWO artifacts in the same context (evaluator loads
|
|
370
|
+
// artificer+scribe; diag_router loads rootcause+distiller). Only the EDGE
|
|
371
|
+
// predecessor may land in predecessorSummary. The wiring helper
|
|
372
|
+
// (attachSummaryEnvelope) only ever receives ONE predecessor object — the
|
|
373
|
+
// caller (runner) is responsible for passing the edge one. This test proves the
|
|
374
|
+
// single-predecessor contract holds regardless of what else is in memory.
|
|
375
|
+
describe('Layer 0 — CP-35 direct-predecessor uniqueness (single predecessor per write)', () => {
|
|
376
|
+
it('each call writes at most one predecessorSummary — the object the caller passed', () => {
|
|
377
|
+
const deps = createMockDeps();
|
|
378
|
+
const runner = new Layer0TestRunner(deps, makeEffectiveConfig({ summaryEnabled: true }));
|
|
379
|
+
// Simulate evaluator's multi-artifact context: it loaded BOTH artificer
|
|
380
|
+
// (edge predecessor) AND scribe (non-edge). Only artificer may go into
|
|
381
|
+
// predecessorSummary. The wiring contract: the runner passes ONLY the edge
|
|
382
|
+
// predecessor; scribe flows through its separate path.
|
|
383
|
+
const artificerContent = { affectedTools: ['t1'], implementationSummary: 'impl', sourceTrace: { scribeArtifactId: 'scribe-art-1' } };
|
|
384
|
+
const scribeContent = { principleDraft: { statement: 'principle' } };
|
|
385
|
+
// Evaluator passes ONLY artificer as loadedPredecessor (edge predecessor).
|
|
386
|
+
const result = runner.callBuildArtifactContentJson(TASK_ID, 'evaluator', { evaluation: { decision: 'approved', concerns: [] }, codeReview: { intentConsistency: { aligned: true } } }, makePredecessor('artificer', artificerContent));
|
|
387
|
+
const parsed = JSON.parse(result);
|
|
388
|
+
const predSummary = parsed.predecessorSummary;
|
|
389
|
+
// Exactly one predecessorSummary, and it's the artificer (edge).
|
|
390
|
+
expect(predSummary).toBeDefined();
|
|
391
|
+
expect(predSummary.runnerKind).toBe('artificer');
|
|
392
|
+
expect(predSummary.artifactId).toBe('pred-artificer');
|
|
393
|
+
// The scribe content (a sentinel string) must NOT appear anywhere in the
|
|
394
|
+
// written contentJson — it was never passed in.
|
|
395
|
+
expect(result).not.toContain(JSON.stringify(scribeContent.principleDraft));
|
|
396
|
+
});
|
|
397
|
+
it('passing the non-edge predecessor (scribe) is the caller\'s contract violation, but the helper still writes exactly one', () => {
|
|
398
|
+
// This documents the contract boundary: attachSummaryEnvelope trusts the
|
|
399
|
+
// caller to pass the edge predecessor. If a caller passes the wrong one,
|
|
400
|
+
// the helper still writes exactly ONE predecessorSummary — it never
|
|
401
|
+
// merges multiple. This is the invariant CP-35 guards.
|
|
402
|
+
const deps = createMockDeps();
|
|
403
|
+
const runner = new Layer0TestRunner(deps, makeEffectiveConfig({ summaryEnabled: true }));
|
|
404
|
+
const result = runner.callBuildArtifactContentJson(TASK_ID, 'evaluator', { evaluation: { decision: 'approved', concerns: [] } }, makePredecessor('scribe', { principleDraft: { statement: 'p' } }));
|
|
405
|
+
const parsed = JSON.parse(result);
|
|
406
|
+
const predSummary = parsed.predecessorSummary;
|
|
407
|
+
expect(predSummary).toBeDefined();
|
|
408
|
+
// Exactly one — no array, no merge.
|
|
409
|
+
expect(typeof predSummary).toBe('object');
|
|
410
|
+
expect(Array.isArray(predSummary)).toBe(false);
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
//# sourceMappingURL=summary-envelope-writer.property.test.js.map
|
package/dist/runtime-v2/internalization/__tests__/summary-envelope-writer.property.test.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary-envelope-writer.property.test.js","sourceRoot":"","sources":["../../../../src/runtime-v2/internalization/__tests__/summary-envelope-writer.property.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Property tests for `checkPredecessorSummaryFreshness` (design §6.1, tasks
|
|
3
|
+
* 3.9–3.10).
|
|
4
|
+
*
|
|
5
|
+
* CP-09: freshness ⟺ content hash equality
|
|
6
|
+
* CP-10: updatedAt does not participate in freshness determination
|
|
7
|
+
*
|
|
8
|
+
* @see .kiro/specs/internalization-progressive-disclosure/design.md §6.1, §16
|
|
9
|
+
* @see .kiro/specs/internalization-progressive-disclosure/requirements.md Requirement 3
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=summary-freshness.property.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary-freshness.property.test.d.ts","sourceRoot":"","sources":["../../../../src/runtime-v2/internalization/__tests__/summary-freshness.property.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Property tests for `checkPredecessorSummaryFreshness` (design §6.1, tasks
|
|
3
|
+
* 3.9–3.10).
|
|
4
|
+
*
|
|
5
|
+
* CP-09: freshness ⟺ content hash equality
|
|
6
|
+
* CP-10: updatedAt does not participate in freshness determination
|
|
7
|
+
*
|
|
8
|
+
* @see .kiro/specs/internalization-progressive-disclosure/design.md §6.1, §16
|
|
9
|
+
* @see .kiro/specs/internalization-progressive-disclosure/requirements.md Requirement 3
|
|
10
|
+
*/
|
|
11
|
+
import { describe, it, expect } from 'vitest';
|
|
12
|
+
import fc from 'fast-check';
|
|
13
|
+
import { createHash } from 'node:crypto';
|
|
14
|
+
import { computeContentHash } from '../artifact-content-hash.js';
|
|
15
|
+
import { checkPredecessorSummaryFreshness, } from '../artifact-summary.js';
|
|
16
|
+
const sha256 = (input) => createHash('sha256').update(input).digest('hex');
|
|
17
|
+
/** Counting store stub — proves zero artifact-store reads happen inside the freshness check. */
|
|
18
|
+
class CountingStoreStub {
|
|
19
|
+
reads = 0;
|
|
20
|
+
getArtifactById() {
|
|
21
|
+
this.reads += 1;
|
|
22
|
+
throw new Error('checkPredecessorSummaryFreshness must not read the artifact store');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function makeSummary() {
|
|
26
|
+
return {
|
|
27
|
+
schemaVersion: 1,
|
|
28
|
+
runnerKind: 'scribe',
|
|
29
|
+
headline: 'headline',
|
|
30
|
+
fields: { principleText: 'text' },
|
|
31
|
+
derivedFrom: 'structured_output',
|
|
32
|
+
omittedFields: [],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function makeRef(contentJson) {
|
|
36
|
+
return {
|
|
37
|
+
artifactId: 'artifact-1',
|
|
38
|
+
runnerKind: 'scribe',
|
|
39
|
+
contentHash: computeContentHash(contentJson, sha256),
|
|
40
|
+
summary: makeSummary(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const jsonSafeValueGen = fc.jsonValue({ maxDepth: 3 });
|
|
44
|
+
const jsonSafeObjectGen = fc.dictionary(fc.string({ minLength: 1, maxLength: 10 }), jsonSafeValueGen, { minKeys: 1, maxKeys: 6 });
|
|
45
|
+
describe('checkPredecessorSummaryFreshness — CP-09 freshness ⟺ content hash equality', () => {
|
|
46
|
+
it('identity: unchanged content is always fresh', () => {
|
|
47
|
+
fc.assert(fc.property(jsonSafeObjectGen, (content) => {
|
|
48
|
+
const ref = makeRef(content);
|
|
49
|
+
expect(checkPredecessorSummaryFreshness(ref, content, sha256)).toEqual({ fresh: true });
|
|
50
|
+
}), { numRuns: 100 });
|
|
51
|
+
});
|
|
52
|
+
it('value change on an existing key: hash mismatch → stale', () => {
|
|
53
|
+
fc.assert(fc.property(jsonSafeObjectGen, fc.string({ minLength: 1, maxLength: 5 }), jsonSafeValueGen, (content, key, newValue) => {
|
|
54
|
+
const ref = makeRef(content);
|
|
55
|
+
const mutated = { ...content, [key]: newValue };
|
|
56
|
+
// Only assert staleness when the mutation actually changes the
|
|
57
|
+
// canonical serialization (guards against the rare case where
|
|
58
|
+
// newValue happens to canonicalize identically to the old value,
|
|
59
|
+
// or the key didn't previously exist and content is empty).
|
|
60
|
+
const before = computeContentHash(content, sha256);
|
|
61
|
+
const after = computeContentHash(mutated, sha256);
|
|
62
|
+
fc.pre(before !== after);
|
|
63
|
+
expect(checkPredecessorSummaryFreshness(ref, mutated, sha256)).toEqual({
|
|
64
|
+
fresh: false,
|
|
65
|
+
reason: 'content_hash_mismatch',
|
|
66
|
+
});
|
|
67
|
+
}), { numRuns: 200 });
|
|
68
|
+
});
|
|
69
|
+
it('key deletion: hash mismatch → stale (when content actually shrinks)', () => {
|
|
70
|
+
fc.assert(fc.property(jsonSafeObjectGen, (content) => {
|
|
71
|
+
const keys = Object.keys(content);
|
|
72
|
+
const firstKey = keys[0];
|
|
73
|
+
fc.pre(firstKey !== undefined);
|
|
74
|
+
fc.pre(keys.length > 0);
|
|
75
|
+
const ref = makeRef(content);
|
|
76
|
+
const { [firstKey]: _removed, ...rest } = content;
|
|
77
|
+
const before = computeContentHash(content, sha256);
|
|
78
|
+
const after = computeContentHash(rest, sha256);
|
|
79
|
+
fc.pre(before !== after);
|
|
80
|
+
expect(checkPredecessorSummaryFreshness(ref, rest, sha256)).toEqual({
|
|
81
|
+
fresh: false,
|
|
82
|
+
reason: 'content_hash_mismatch',
|
|
83
|
+
});
|
|
84
|
+
}), { numRuns: 150 });
|
|
85
|
+
});
|
|
86
|
+
it('key addition: hash mismatch → stale', () => {
|
|
87
|
+
fc.assert(fc.property(jsonSafeObjectGen, jsonSafeValueGen, (content, newValue) => {
|
|
88
|
+
const ref = makeRef(content);
|
|
89
|
+
const added = { ...content, __new_key__: newValue };
|
|
90
|
+
expect(checkPredecessorSummaryFreshness(ref, added, sha256)).toEqual({
|
|
91
|
+
fresh: false,
|
|
92
|
+
reason: 'content_hash_mismatch',
|
|
93
|
+
});
|
|
94
|
+
}), { numRuns: 100 });
|
|
95
|
+
});
|
|
96
|
+
it('setting an existing key to undefined (JSON-equivalent to deletion): matches JSON semantics', () => {
|
|
97
|
+
fc.assert(fc.property(jsonSafeObjectGen, (content) => {
|
|
98
|
+
const keys = Object.keys(content);
|
|
99
|
+
const firstKey = keys[0];
|
|
100
|
+
fc.pre(firstKey !== undefined);
|
|
101
|
+
fc.pre(keys.length > 0);
|
|
102
|
+
const ref = makeRef(content);
|
|
103
|
+
const mutated = { ...content, [firstKey]: undefined };
|
|
104
|
+
const before = computeContentHash(content, sha256);
|
|
105
|
+
const after = computeContentHash(mutated, sha256);
|
|
106
|
+
const result = checkPredecessorSummaryFreshness(ref, mutated, sha256);
|
|
107
|
+
if (before === after) {
|
|
108
|
+
expect(result).toEqual({ fresh: true });
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
expect(result).toEqual({ fresh: false, reason: 'content_hash_mismatch' });
|
|
112
|
+
}
|
|
113
|
+
}), { numRuns: 100 });
|
|
114
|
+
});
|
|
115
|
+
it('missing predecessor content → predecessor_missing, distinct from a hash mismatch', () => {
|
|
116
|
+
const ref = makeRef({ a: 1 });
|
|
117
|
+
expect(checkPredecessorSummaryFreshness(ref, undefined, sha256)).toEqual({
|
|
118
|
+
fresh: false,
|
|
119
|
+
reason: 'predecessor_missing',
|
|
120
|
+
});
|
|
121
|
+
expect(checkPredecessorSummaryFreshness(undefined, { a: 1 }, sha256)).toEqual({
|
|
122
|
+
fresh: false,
|
|
123
|
+
reason: 'predecessor_missing',
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
it('performs zero artifact-store reads (no I/O; predecessor must already be in memory)', () => {
|
|
127
|
+
const store = new CountingStoreStub();
|
|
128
|
+
const content = { a: 1, b: 'text' };
|
|
129
|
+
const ref = makeRef(content);
|
|
130
|
+
checkPredecessorSummaryFreshness(ref, content, sha256);
|
|
131
|
+
checkPredecessorSummaryFreshness(ref, undefined, sha256);
|
|
132
|
+
checkPredecessorSummaryFreshness(undefined, content, sha256);
|
|
133
|
+
expect(store.reads).toBe(0);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
describe('checkPredecessorSummaryFreshness — CP-10 updatedAt does not participate', () => {
|
|
137
|
+
it('is unaffected by a pending→validated-style timestamp-only refresh (memory store shape)', () => {
|
|
138
|
+
fc.assert(fc.property(jsonSafeObjectGen, fc.string({ minLength: 1, maxLength: 24 }), (content, newUpdatedAt) => {
|
|
139
|
+
// Simulate a memory-store record: contentJson is untouched, only a
|
|
140
|
+
// sibling `updatedAt` field on the *record* (not inside contentJson)
|
|
141
|
+
// changes. checkPredecessorSummaryFreshness only ever receives the
|
|
142
|
+
// contentJson itself, so it structurally cannot see updatedAt.
|
|
143
|
+
const ref = makeRef(content);
|
|
144
|
+
const recordAfterRefresh = { contentJson: content, updatedAt: newUpdatedAt };
|
|
145
|
+
expect(checkPredecessorSummaryFreshness(ref, recordAfterRefresh.contentJson, sha256)).toEqual({
|
|
146
|
+
fresh: true,
|
|
147
|
+
});
|
|
148
|
+
}), { numRuns: 100 });
|
|
149
|
+
});
|
|
150
|
+
it('is unaffected by a pending→validated-style timestamp-only refresh (sqlite upsert shape)', () => {
|
|
151
|
+
fc.assert(fc.property(jsonSafeObjectGen, fc.integer({ min: 0, max: 2_000_000_000 }), (content, newUpdatedAtEpoch) => {
|
|
152
|
+
// Simulate a sqlite row: contentJson column unchanged, updated_at
|
|
153
|
+
// column bumped by an upsert. Same structural argument as above —
|
|
154
|
+
// the row's updated_at is never passed into the freshness check.
|
|
155
|
+
const ref = makeRef(content);
|
|
156
|
+
const rowAfterUpsert = { content_json: JSON.stringify(content), updated_at: newUpdatedAtEpoch };
|
|
157
|
+
const parsedContentJson = JSON.parse(rowAfterUpsert.content_json);
|
|
158
|
+
expect(checkPredecessorSummaryFreshness(ref, parsedContentJson, sha256)).toEqual({
|
|
159
|
+
fresh: true,
|
|
160
|
+
});
|
|
161
|
+
}), { numRuns: 100 });
|
|
162
|
+
});
|
|
163
|
+
it('freshness result does not change when only an out-of-band updatedAt-like value varies, content held fixed', () => {
|
|
164
|
+
const content = { statement: 'fixed content', scope: ['a', 'b'] };
|
|
165
|
+
const ref = makeRef(content);
|
|
166
|
+
const timestamps = ['2026-01-01T00:00:00Z', '2026-06-15T12:30:00Z', '2027-12-31T23:59:59Z'];
|
|
167
|
+
for (const _ts of timestamps) {
|
|
168
|
+
// The function signature has no updatedAt parameter at all — this test
|
|
169
|
+
// documents that fact by construction: there is no way to pass a
|
|
170
|
+
// timestamp into checkPredecessorSummaryFreshness that could influence
|
|
171
|
+
// its result.
|
|
172
|
+
expect(checkPredecessorSummaryFreshness(ref, content, sha256)).toEqual({ fresh: true });
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
//# sourceMappingURL=summary-freshness.property.test.js.map
|