mirror of
https://github.com/stablyai/orca.git
synced 2026-10-19 00:03:20 +00:00
* chore(lint): upgrade oxlint to 1.71 and enable 7 new rules Upgrade oxlint 1.67.0 -> 1.71.0 (1.72 was blocked by the repo's 3-day minimum-release-age supply-chain guard; nothing here needs it). The bump is a no-op on the existing config. Enable 3 error rules (backlog autofixed to zero in this commit) and 4 warn rules (surface signal without gating CI): error (autofixed, behavior-preserving): - unicorn/prefer-node-protocol (~1531 sites: bare builtin -> node:) - typescript/no-import-type-side-effects (~36: all-inline-type -> import type) - unicorn/no-array-reverse (19: copy-then-reverse -> toReversed) warn (real signal, current fires are test-only/correct): - unicorn/no-array-fill-with-reference-type (aliasing footgun guard) - typescript/no-unsafe-function-type (bans bare Function type) - unicorn/prefer-array-flat-map (map().flat() -> flatMap()) - unicorn/prefer-regexp-test (.match() in bool ctx -> .test()) mobile/.oxlintrc.json extends root, so it inherits all 7; the autofix ran from root and covered mobile/ too. Verification (all green): oxlint 0 errors (root+mobile+aux configs), oxfmt clean, typecheck (node+cli+web), vitest 22795 passed / 0 failed, builds (electron-vite + web + cli) succeed. node: rewrites confirmed to skip embedded SSH/CLI string payloads (AST-only); all toReversed sites verified to operate on fresh copies or write-once locals. * chore(lint): bump mobile oxlint to 1.71 so inherited rules parse mobile/ is a standalone pnpm project pinning its own oxlint@1.67, which lacks unicorn/no-array-fill-with-reference-type (needs >=1.70). Since mobile/.oxlintrc.json extends the root config, mobile CI's 'cd mobile && oxlint' failed to parse the new rule. Bump mobile to match root (1.71). Verified in mobile/: oxlint 0 errors, oxfmt --check clean, tsc --noEmit pass, vitest 978 passed / 0 failed. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
108 lines
2.9 KiB
TypeScript
108 lines
2.9 KiB
TypeScript
import { createHash } from 'node:crypto'
|
|
import { resolve } from 'node:path'
|
|
import { recordCoalescedCrashBreadcrumb } from '../crash-reporting/crash-breadcrumb-store'
|
|
|
|
const VALIDATION_BACKOFF_TTL_MS = 5 * 60_000
|
|
const MAX_VALIDATION_BACKOFF_ENTRIES = 256
|
|
const VALIDATION_BREADCRUMB_MIN_INTERVAL_MS = 30_000
|
|
|
|
export type PRRefreshValidationDenialReason =
|
|
| 'unknown-repo'
|
|
| 'repo-path-mismatch'
|
|
| 'host-mismatch'
|
|
|
|
type ValidationBackoffIdentity = {
|
|
repoId?: string | null
|
|
repoPath: string
|
|
reason: PRRefreshValidationDenialReason
|
|
}
|
|
|
|
type ValidationBackoffEntry = {
|
|
expiresAt: number
|
|
}
|
|
|
|
type ValidationBackoffCounters = {
|
|
recorded: number
|
|
skipped: number
|
|
expired: number
|
|
}
|
|
|
|
const validationBackoff = new Map<string, ValidationBackoffEntry>()
|
|
const counters: ValidationBackoffCounters = {
|
|
recorded: 0,
|
|
skipped: 0,
|
|
expired: 0
|
|
}
|
|
|
|
function validationIdentityKey(identity: ValidationBackoffIdentity): string {
|
|
return [identity.repoId ?? '', resolve(identity.repoPath), identity.reason].join('\0')
|
|
}
|
|
|
|
function validationIdentityToken(key: string): string {
|
|
return createHash('sha256').update(key).digest('hex').slice(0, 12)
|
|
}
|
|
|
|
function evictOldestValidationBackoffEntries(): void {
|
|
while (validationBackoff.size > MAX_VALIDATION_BACKOFF_ENTRIES) {
|
|
const oldest = validationBackoff.keys().next()
|
|
if (oldest.done) {
|
|
break
|
|
}
|
|
validationBackoff.delete(oldest.value)
|
|
}
|
|
}
|
|
|
|
function recordValidationBreadcrumb(
|
|
reason: PRRefreshValidationDenialReason,
|
|
result: 'recorded' | 'backoff',
|
|
token: string
|
|
): void {
|
|
recordCoalescedCrashBreadcrumb({
|
|
name: 'pr_refresh_validation_skip',
|
|
coalesceKey: `pr-refresh-validation:${reason}:${token}`,
|
|
minIntervalMs: VALIDATION_BREADCRUMB_MIN_INTERVAL_MS,
|
|
data: {
|
|
reason,
|
|
result,
|
|
token,
|
|
recorded: counters.recorded,
|
|
skipped: counters.skipped,
|
|
expired: counters.expired
|
|
}
|
|
})
|
|
}
|
|
|
|
export function notePRRefreshValidationDenial(
|
|
identity: ValidationBackoffIdentity,
|
|
nowMs = Date.now()
|
|
): 'validation-denied' | 'validation-backoff' {
|
|
const key = validationIdentityKey(identity)
|
|
const existing = validationBackoff.get(key)
|
|
const token = validationIdentityToken(key)
|
|
if (existing && existing.expiresAt > nowMs) {
|
|
counters.skipped += 1
|
|
recordValidationBreadcrumb(identity.reason, 'backoff', token)
|
|
return 'validation-backoff'
|
|
}
|
|
if (existing) {
|
|
counters.expired += 1
|
|
validationBackoff.delete(key)
|
|
}
|
|
counters.recorded += 1
|
|
validationBackoff.set(key, { expiresAt: nowMs + VALIDATION_BACKOFF_TTL_MS })
|
|
evictOldestValidationBackoffEntries()
|
|
recordValidationBreadcrumb(identity.reason, 'recorded', token)
|
|
return 'validation-denied'
|
|
}
|
|
|
|
export function clearPRRefreshValidationBackoffForTests(): void {
|
|
validationBackoff.clear()
|
|
counters.recorded = 0
|
|
counters.skipped = 0
|
|
counters.expired = 0
|
|
}
|
|
|
|
export function getPRRefreshValidationBackoffCountForTests(): number {
|
|
return validationBackoff.size
|
|
}
|