fix(setup): preserve preflight guarantees across ownership filtering

This commit is contained in:
haelyra
2026-09-07 16:53:53 -04:00
parent adb39a13c9
commit ba3a64a2c5
4 changed files with 83 additions and 19 deletions
+20 -2
View File
@@ -82,8 +82,26 @@ Successful managed-file upgrades retain their existing replacement semantics.
## Completion evidence
First batch 82bfd225 passed 4,215/4,215 tests and lint. Integrated full validation
and hosted checks are pending. Signing remains unavailable locally.
First batch 82bfd225 passed 4,215/4,215 tests and lint. The first combined run
at 8cc31f1e passed 4,370/4,372 tests, with 89.27% line and 81.52% branch coverage.
Its two failures exposed guided setup reporting success after a late collision
was filtered. Full-preview revalidation fixes that interaction; all 22 guided
setup tests now pass, including initially identical unowned files before later
writes. Final full-suite and hosted validation are pending.
Windows hosted checks exposed fixture-owned descriptor cleanup and directory
rename assumptions in two new settings tests. The repaired fixtures preserve
Windows OS-refusal assertions and ECC parent-identity checks. CodeQL findings
338-341 were confined to test-source patterns; minimal assertion/interception
changes preserve coverage without alert dismissals. Hosted rescanning remains
required.
The first combined packed artifact passed the isolated macOS lifecycle, 13
memory MCP regressions, 12 actual Codex/Hermes protocol sessions, and 196
GateGuard cases including quoted, unquoted, and tab-stripped heredocs. Package
helpers, public CLI aliases, and dry-run entrypoints were exercised from the
installed archive, not just the source checkout. Final source must be repacked
after the guided-setup integration repair. Signing remains unavailable locally.
Pending final hosted validation, signed tag, publication, registry
integrity readback, and clean lifecycle canaries. This document does not claim
+4 -1
View File
@@ -14,9 +14,12 @@ and signing evidence are tracked separately in the release checklist.
absolute exemptions remain supported
([#2921](https://github.com/affaan-m/ECC/issues/2921)).
- Installer writes reject collisions with untracked user-owned files. Failed
installs checkpoint only files they actually wrote, preserving the previous
installs refresh ownership hashes only for files they actually wrote, preserving the previous
ownership hashes of untouched managed files
([#2964](https://github.com/affaan-m/ECC/issues/2964)).
- Guided setup revalidates its preview before ownership filtering, so files
appearing between preview and apply cause a clear retry instead of a false
success. Existing identical user files stay outside ECC ownership.
- Uninstall respects `ECC_DRY_RUN=1`, including legacy Codex paths, and rejects
invalid dry-run values instead of silently allowing deletion
([#2952](https://github.com/affaan-m/ECC/issues/2952)).
+27 -16
View File
@@ -373,7 +373,9 @@ async function applyPreflightedManagedPlan(entry) {
: preflightManagedPlan(entry.preview.plan);
const ownedDestinations = new Set(preview.ownershipSnapshot.destinations);
let expectedStateFingerprint = preview.ownershipSnapshot.stateFingerprint;
let operationIndex = 0;
const expectedOperations = new Map(preview.operations.map(operation => [
canonicalPath(operation.destinationPath), operation,
]));
const assertStateUnchanged = () => (
assertInstallStateUnchanged(preview.plan, expectedStateFingerprint)
);
@@ -381,26 +383,35 @@ async function applyPreflightedManagedPlan(entry) {
assertStateUnchanged();
expectedStateFingerprint = fingerprintInstallStateValue(state);
};
const assertOperationUnchanged = operation => {
const destination = canonicalPath(operation.destinationPath);
const expected = expectedOperations.get(destination);
const currentClassification = classifyManagedOperation(operation, ownedDestinations);
if (
!expected
|| expected.kind !== operation.kind
|| expected.classification !== currentClassification
) {
throw new Error(
`Refusing to write ${operation.destinationPath}: destination changed after Kimi preflight.`
);
}
return destination;
};
const result = require('./install-executor').applyInstallPlan(preview.plan, {
beforeInstallStateRead: assertStateUnchanged,
beforeInstallStateRead() {
assertStateUnchanged();
// Check the original preview before ownership filtering can skip a late
// collision. Guided setup must report the changed plan as a failure.
for (const operation of preview.plan.operations) {
assertOperationUnchanged(operation);
}
},
beforeOperationWrite({ operation }) {
assertStateUnchanged();
const expected = preview.operations[operationIndex];
const currentClassification = classifyManagedOperation(operation, ownedDestinations);
const destination = canonicalPath(operation.destinationPath);
if (
!expected
|| expected.kind !== operation.kind
|| canonicalPath(expected.destinationPath) !== destination
|| expected.classification !== currentClassification
) {
throw new Error(
`Refusing to write ${operation.destinationPath}: destination changed after Kimi preflight.`
);
}
const destination = assertOperationUnchanged(operation);
ownedDestinations.add(destination);
operationIndex += 1;
},
beforeInstallStateWrite: prepareInstallStateWrite,
});
+32
View File
@@ -569,6 +569,38 @@ function writeManagedState(plan, overrides = {}) {
}
});
await test('preserves an initially identical user file without shifting later write checks', async () => {
const root = tempDir('ecc-guided-identical-preserved-');
const projection = require('../../scripts/lib/install-state-store-sync');
const originalProjection = projection.projectCanonicalInstallState;
// This case verifies canonical ownership, not the optional derived cache.
projection.projectCanonicalInstallState = async () => ({ status: 'projected' });
try {
const source = path.join(root, 'source.md');
const userFile = path.join(root, '.kimi-code', 'rules', 'existing.md');
const newFile = path.join(root, '.kimi-code', 'rules', 'new.md');
writeFile(source, 'ecc\n');
writeFile(userFile, 'ecc\n');
const plan = managedPlan(root, [
stateOperation(userFile, { sourcePath: source }),
stateOperation(newFile, { sourcePath: source }),
]);
const result = await applyMultiHarnessPlan({
harnesses: [{ id: 'kimi', preview: preflightManagedPlan(plan) }],
request: { harnesses: ['kimi'] },
});
assert.strictEqual(result.status, 'complete');
assert.strictEqual(fs.readFileSync(userFile, 'utf8'), 'ecc\n');
assert.strictEqual(fs.readFileSync(newFile, 'utf8'), 'ecc\n');
const state = JSON.parse(fs.readFileSync(plan.installStatePath, 'utf8'));
assert.ok(!state.operations.some(operation => operation.destinationPath === userFile));
assert.ok(state.operations.some(operation => operation.destinationPath === newFile));
} finally {
projection.projectCanonicalInstallState = originalProjection;
fs.rmSync(root, { recursive: true, force: true });
}
});
await test('refuses conflicting JSON created after preview but before apply', async () => {
const root = tempDir('ecc-guided-late-json-collision-');
try {