mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-13 13:17:55 +02:00
fix(setup): preserve preflight guarantees across ownership filtering
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)).
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user