mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-18 15:50:25 +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
|
## Completion evidence
|
||||||
|
|
||||||
First batch 82bfd225 passed 4,215/4,215 tests and lint. Integrated full validation
|
First batch 82bfd225 passed 4,215/4,215 tests and lint. The first combined run
|
||||||
and hosted checks are pending. Signing remains unavailable locally.
|
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
|
Pending final hosted validation, signed tag, publication, registry
|
||||||
integrity readback, and clean lifecycle canaries. This document does not claim
|
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
|
absolute exemptions remain supported
|
||||||
([#2921](https://github.com/affaan-m/ECC/issues/2921)).
|
([#2921](https://github.com/affaan-m/ECC/issues/2921)).
|
||||||
- Installer writes reject collisions with untracked user-owned files. Failed
|
- 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
|
ownership hashes of untouched managed files
|
||||||
([#2964](https://github.com/affaan-m/ECC/issues/2964)).
|
([#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
|
- Uninstall respects `ECC_DRY_RUN=1`, including legacy Codex paths, and rejects
|
||||||
invalid dry-run values instead of silently allowing deletion
|
invalid dry-run values instead of silently allowing deletion
|
||||||
([#2952](https://github.com/affaan-m/ECC/issues/2952)).
|
([#2952](https://github.com/affaan-m/ECC/issues/2952)).
|
||||||
|
|||||||
@@ -373,7 +373,9 @@ async function applyPreflightedManagedPlan(entry) {
|
|||||||
: preflightManagedPlan(entry.preview.plan);
|
: preflightManagedPlan(entry.preview.plan);
|
||||||
const ownedDestinations = new Set(preview.ownershipSnapshot.destinations);
|
const ownedDestinations = new Set(preview.ownershipSnapshot.destinations);
|
||||||
let expectedStateFingerprint = preview.ownershipSnapshot.stateFingerprint;
|
let expectedStateFingerprint = preview.ownershipSnapshot.stateFingerprint;
|
||||||
let operationIndex = 0;
|
const expectedOperations = new Map(preview.operations.map(operation => [
|
||||||
|
canonicalPath(operation.destinationPath), operation,
|
||||||
|
]));
|
||||||
const assertStateUnchanged = () => (
|
const assertStateUnchanged = () => (
|
||||||
assertInstallStateUnchanged(preview.plan, expectedStateFingerprint)
|
assertInstallStateUnchanged(preview.plan, expectedStateFingerprint)
|
||||||
);
|
);
|
||||||
@@ -381,26 +383,35 @@ async function applyPreflightedManagedPlan(entry) {
|
|||||||
assertStateUnchanged();
|
assertStateUnchanged();
|
||||||
expectedStateFingerprint = fingerprintInstallStateValue(state);
|
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, {
|
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 }) {
|
beforeOperationWrite({ operation }) {
|
||||||
assertStateUnchanged();
|
assertStateUnchanged();
|
||||||
const expected = preview.operations[operationIndex];
|
const destination = assertOperationUnchanged(operation);
|
||||||
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.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
ownedDestinations.add(destination);
|
ownedDestinations.add(destination);
|
||||||
operationIndex += 1;
|
|
||||||
},
|
},
|
||||||
beforeInstallStateWrite: prepareInstallStateWrite,
|
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 () => {
|
await test('refuses conflicting JSON created after preview but before apply', async () => {
|
||||||
const root = tempDir('ecc-guided-late-json-collision-');
|
const root = tempDir('ecc-guided-late-json-collision-');
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user