mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-17 05:35:36 +02:00
fix: bound observer counters and apply first-tick thresholds
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
"id": "hand-seeded",
|
||||
"source": "manual-curation-pending-regeneration"
|
||||
},
|
||||
"registryDigest": "55e9ce5724329ea6315394ed8969fc23d820f1163d82a9f3e1baef304e7ca30f",
|
||||
"registryDigest": "3a5aa65956a41c77902b6e3287d8352f04a06e04343decec804d0a5e93e538f5",
|
||||
"schemaVersion": 1,
|
||||
"triggers": {
|
||||
"skill:agent-runtime-gateway-smoke-test": [
|
||||
|
||||
@@ -586,22 +586,21 @@ touch "$ACTIVITY_FILE" 2>/dev/null || true
|
||||
should_signal=0
|
||||
|
||||
_clv2_bump_signal_counter() {
|
||||
counter=0
|
||||
if [ -f "$SIGNAL_COUNTER_FILE" ]; then
|
||||
counter=$(cat "$SIGNAL_COUNTER_FILE" 2>/dev/null || echo 0)
|
||||
# Guard against a corrupt counter file: a non-integer value would abort the
|
||||
# hook under `set -e` at the arithmetic below.
|
||||
case "$counter" in
|
||||
''|*[!0-9]*) counter=0 ;;
|
||||
esac
|
||||
counter=$((counter + 1))
|
||||
if [ "$counter" -ge "$SIGNAL_EVERY_N" ]; then
|
||||
should_signal=1
|
||||
counter=0
|
||||
fi
|
||||
echo "$counter" > "$SIGNAL_COUNTER_FILE"
|
||||
else
|
||||
echo "1" > "$SIGNAL_COUNTER_FILE"
|
||||
counter=$(head -c 20 "$SIGNAL_COUNTER_FILE" 2>/dev/null || echo 0)
|
||||
fi
|
||||
# Bound parsing and avoid shell arithmetic overflow on corrupt local state.
|
||||
case "$counter" in
|
||||
''|*[!0-9]*) counter=0 ;;
|
||||
esac
|
||||
if [ "${#counter}" -gt 18 ]; then counter=0; fi
|
||||
counter=$((10#$counter + 1))
|
||||
if [ "$counter" -ge "$SIGNAL_EVERY_N" ]; then
|
||||
should_signal=1
|
||||
counter=0
|
||||
fi
|
||||
echo "$counter" > "$SIGNAL_COUNTER_FILE"
|
||||
}
|
||||
|
||||
if command -v flock >/dev/null 2>&1 && exec 8>"$SIGNAL_COUNTER_LOCK" 2>/dev/null; then
|
||||
@@ -655,14 +654,14 @@ try:
|
||||
time.sleep(0.02)
|
||||
try:
|
||||
with open(sys.argv[2], encoding="ascii") as source:
|
||||
value = source.read().strip()
|
||||
counter = int(value) if value and value.isascii() and value.isdigit() else 0
|
||||
counter += 1
|
||||
signal = int(counter >= int(sys.argv[3]))
|
||||
if signal:
|
||||
counter = 0
|
||||
value = source.read(20).strip()
|
||||
except FileNotFoundError:
|
||||
counter, signal = 1, 0
|
||||
value = ""
|
||||
counter = int(value) if 0 < len(value) <= 18 and value.isascii() and value.isdigit() else 0
|
||||
counter += 1
|
||||
signal = int(counter >= int(sys.argv[3]))
|
||||
if signal:
|
||||
counter = 0
|
||||
with open(sys.argv[2], "w", encoding="ascii") as target:
|
||||
target.write(str(counter) + "\n")
|
||||
print(signal)
|
||||
|
||||
@@ -269,6 +269,30 @@ async function runStrandedLock() {
|
||||
}
|
||||
}
|
||||
|
||||
async function runCounterBoundary(initial, threshold, expected, forcePython) {
|
||||
const { testDir, projectDir, testObserve } = buildSandbox();
|
||||
try {
|
||||
if (initial !== null) {
|
||||
fs.writeFileSync(path.join(projectDir, '.observer-signal-counter'), initial);
|
||||
}
|
||||
const extraEnv = { ECC_OBSERVER_SIGNAL_EVERY_N: String(threshold) };
|
||||
if (forcePython || !hasFlock) {
|
||||
const bashEnv = path.join(testDir, 'no-flock.sh');
|
||||
const fixture = forcePython
|
||||
? 'command() { if [ "$1" = "-v" ] && [ "$2" = "flock" ]; then return 1; fi; builtin command "$@"; }\n'
|
||||
// Exercise the real shell counter on macOS with an inherited-fd OS
|
||||
// lock fixture. Native GNU flock remains a Linux platform check.
|
||||
: `flock() { python3 -c 'import fcntl,sys; fcntl.flock(int(sys.argv[-1]), fcntl.LOCK_UN if sys.argv[1] == "-u" else fcntl.LOCK_EX)' "$@"; }\n`;
|
||||
fs.writeFileSync(bashEnv, fixture);
|
||||
extraEnv.BASH_ENV = bashEnv;
|
||||
}
|
||||
await runObserve(testObserve, projectDir, extraEnv);
|
||||
assert.strictEqual(readCounter(projectDir), expected);
|
||||
} finally {
|
||||
cleanupDir(testDir);
|
||||
}
|
||||
}
|
||||
|
||||
async function runKilledOwner() {
|
||||
const { testDir, projectDir, testObserve } = buildSandbox();
|
||||
let owner;
|
||||
@@ -305,6 +329,17 @@ async function runKilledOwner() {
|
||||
await asyncTest('concurrent invocations never lose a counter increment', runConcurrent);
|
||||
await asyncTest('stranded mkdir lock cannot delay or freeze later observations', runStrandedLock);
|
||||
await asyncTest('busy lock skips safely and a killed owner releases it automatically', runKilledOwner);
|
||||
for (const forcePython of [false, true]) {
|
||||
const label = forcePython ? 'Python fallback' : hasFlock ? 'flock' : 'shell counter with OS-lock fixture';
|
||||
await asyncTest(`${label} applies threshold one to a missing first counter`,
|
||||
() => runCounterBoundary(null, 1, 0, forcePython));
|
||||
await asyncTest(`${label} resets a malformed counter before incrementing`,
|
||||
() => runCounterBoundary('invalid\n', 2, 1, forcePython));
|
||||
await asyncTest(`${label} resets an oversized counter before incrementing`,
|
||||
() => runCounterBoundary('9'.repeat(5000) + '\n', 2, 1, forcePython));
|
||||
await asyncTest(`${label} parses leading-zero counters as decimal`,
|
||||
() => runCounterBoundary('08\n', 20, 9, forcePython));
|
||||
}
|
||||
} else {
|
||||
console.log(' - skipping shell-execution tests (requires non-Windows + python3)');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user