Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .loopover.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -1266,3 +1266,14 @@ settings:
# env var, which stays the fallback when absent.
# upstreamDriftIssues:
# enabled: true # Bool. Default: false (the env var decides instead).

# Opt-in federated fleet intelligence export (#1970): packages this instance's OWN calibration signals into a
# signed, anonymized bundle an operator can choose to hand to a peer (or to a collector they run). Exports
# AGGREGATE figures only -- gate precision, reversal/slop/copycat rates over a window -- never source code,
# diffs, GitHub logins, repo names, PR ids, or raw gate reason text. Operator-level, not per-repo, so only
# meaningful on the loopover self-repo's own manifest. OFF unless you explicitly enable it here: with the block
# absent nothing is bundled and nothing leaves the instance. Distinct from the always-on Orb telemetry (#1255,
# suppressed by ORB_AIR_GAP), which streams per-PR events UP to loopover's central collector -- this one is
# peer-to-peer and assumes no central service at all.
# federatedIntelligence:
# enabled: true # Bool. Default: false (nothing is exported).
11 changes: 11 additions & 0 deletions config/examples/loopover.full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1280,3 +1280,14 @@ settings:
# env var, which stays the fallback when absent.
# upstreamDriftIssues:
# enabled: true # Bool. Default: false (the env var decides instead).

# Opt-in federated fleet intelligence export (#1970): packages this instance's OWN calibration signals into a
# signed, anonymized bundle an operator can choose to hand to a peer (or to a collector they run). Exports
# AGGREGATE figures only -- gate precision, reversal/slop/copycat rates over a window -- never source code,
# diffs, GitHub logins, repo names, PR ids, or raw gate reason text. Operator-level, not per-repo, so only
# meaningful on the loopover self-repo's own manifest. OFF unless you explicitly enable it here: with the block
# absent nothing is bundled and nothing leaves the instance. Distinct from the always-on Orb telemetry (#1255,
# suppressed by ORB_AIR_GAP), which streams per-PR events UP to loopover's central collector -- this one is
# peer-to-peer and assumes no central service at all.
# federatedIntelligence:
# enabled: true # Bool. Default: false (nothing is exported).
53 changes: 52 additions & 1 deletion packages/loopover-engine/src/focus-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,23 @@ export type FocusManifestUpstreamDriftIssuesConfig = {
enabled: boolean;
};

/**
* Config-as-code opt-in for the federated fleet intelligence export (#1970), declared under
* `federatedIntelligence:`. Gates buildFederatedBundle (src/orb/federated-bundle.ts), which packages this
* instance's own anonymized calibration signals into a signed bundle an operator can hand to a peer -- like
* `draftFlow:`/`upstreamDriftIssues:` above, this is read from the loopover self-repo's own manifest, since
* exporting a deployment's calibration data is operator-level (fleet-wide), not per-contributor-repo.
* Mirrors `upstreamDriftIssues:` exactly: no DB-backed counterpart, so the parsed value (or the default
* below when unset) IS the effective value. Unlike those two it overrides no env flag -- there is no
* federated env var; not present ⇒ disabled ⇒ nothing is bundled and no network call is made, byte-identical
* to before this override existed. (ORB_AIR_GAP gates the separate, always-on #1255 orb telemetry path in
* src/selfhost/orb-collector.ts and is unrelated to this opt-in.)
*/
export type FocusManifestFederatedIntelligenceConfig = {
present: boolean;
enabled: boolean;
};

/**
* Generic repository-settings override declared in `.loopover.yml` under `settings:`. A partial of
* {@link RepositorySettings} — every behaviour a maintainer can toggle in the dashboard can be set here
Expand Down Expand Up @@ -1009,6 +1026,7 @@ export type FocusManifest = {
publicStats: FocusManifestPublicStatsConfig;
draftFlow: FocusManifestDraftFlowConfig;
upstreamDriftIssues: FocusManifestUpstreamDriftIssuesConfig;
federatedIntelligence: FocusManifestFederatedIntelligenceConfig;
warnings: string[];
};

Expand Down Expand Up @@ -1170,6 +1188,11 @@ const EMPTY_UPSTREAM_DRIFT_ISSUES_CONFIG: FocusManifestUpstreamDriftIssuesConfig
enabled: false,
};

const EMPTY_FEDERATED_INTELLIGENCE_CONFIG: FocusManifestFederatedIntelligenceConfig = {
present: false,
enabled: false,
};

const EMPTY_MANIFEST: FocusManifest = {
present: false,
source: "none",
Expand All @@ -1193,6 +1216,7 @@ const EMPTY_MANIFEST: FocusManifest = {
publicStats: { ...EMPTY_PUBLIC_STATS_CONFIG },
draftFlow: { ...EMPTY_DRAFT_FLOW_CONFIG },
upstreamDriftIssues: { ...EMPTY_UPSTREAM_DRIFT_ISSUES_CONFIG },
federatedIntelligence: { ...EMPTY_FEDERATED_INTELLIGENCE_CONFIG },
warnings: [],
};

Expand Down Expand Up @@ -1229,6 +1253,7 @@ function emptyManifest(source: FocusManifestSource, warnings: string[] = []): Fo
publicStats: { ...EMPTY_PUBLIC_STATS_CONFIG },
draftFlow: { ...EMPTY_DRAFT_FLOW_CONFIG },
upstreamDriftIssues: { ...EMPTY_UPSTREAM_DRIFT_ISSUES_CONFIG },
federatedIntelligence: { ...EMPTY_FEDERATED_INTELLIGENCE_CONFIG },
};
}

Expand Down Expand Up @@ -2038,6 +2063,30 @@ export function upstreamDriftIssuesConfigToJson(config: FocusManifestUpstreamDri
return { enabled: config.enabled };
}

/**
* Parse the optional `federatedIntelligence:` mapping (#1970). Mirrors {@link parseUpstreamDriftIssuesConfig}
* exactly -- `enabled` is the only field, defaulting to false, so the parsed value IS the effective value and
* an absent block leaves the federated export off.
*/
function parseFederatedIntelligenceConfig(value: JsonValue | undefined, warnings: string[]): FocusManifestFederatedIntelligenceConfig {
if (value === undefined || value === null) return { ...EMPTY_FEDERATED_INTELLIGENCE_CONFIG };
if (typeof value !== "object" || Array.isArray(value)) {
warnings.push('Manifest field "federatedIntelligence" must be a mapping; ignoring it.');
return { ...EMPTY_FEDERATED_INTELLIGENCE_CONFIG };
}
const record = value as Record<string, JsonValue>;
const enabled = normalizeOptionalBoolean(record.enabled, "federatedIntelligence.enabled", warnings) ?? false;
return { present: true, enabled };
}

/** Serialize a federatedIntelligence config back into the parse-compatible shape so a cached snapshot
* round-trips through {@link parseFederatedIntelligenceConfig} unchanged. Returns null when nothing is
* configured. */
export function federatedIntelligenceConfigToJson(config: FocusManifestFederatedIntelligenceConfig): JsonValue {
if (!config.present) return null;
return { enabled: config.enabled };
}

function normalizeOptionalEnum<T extends string>(value: JsonValue | undefined, field: string, allowed: readonly T[], warnings: string[]): T | null {
if (value === undefined || value === null) return null;
if (typeof value === "string" && (allowed as readonly string[]).includes(value)) return value as T;
Expand Down Expand Up @@ -3385,6 +3434,7 @@ export function parseFocusManifest(raw: unknown, source?: FocusManifestSource):
publicStats: parsePublicStatsConfig(record.publicStats, warnings),
draftFlow: parseDraftFlowConfig(record.draftFlow, warnings),
upstreamDriftIssues: parseUpstreamDriftIssuesConfig(record.upstreamDriftIssues, warnings),
federatedIntelligence: parseFederatedIntelligenceConfig(record.federatedIntelligence, warnings),
warnings,
};
if (
Expand All @@ -3407,7 +3457,8 @@ export function parseFocusManifest(raw: unknown, source?: FocusManifestSource):
!manifest.ops.present &&
!manifest.publicStats.present &&
!manifest.draftFlow.present &&
!manifest.upstreamDriftIssues.present
!manifest.upstreamDriftIssues.present &&
!manifest.federatedIntelligence.present
) {
warnings.push("Manifest contained no recognized focus fields; falling back to deterministic signals.");
manifest.present = false;
Expand Down
2 changes: 2 additions & 0 deletions packages/loopover-engine/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,7 @@ export {
publicStatsConfigToJson,
draftFlowConfigToJson,
upstreamDriftIssuesConfigToJson,
federatedIntelligenceConfigToJson,
settingsOverrideToJson,
MAX_FOCUS_MANIFEST_BYTES,
CONVERGED_FEATURE_KEYS,
Expand Down Expand Up @@ -795,6 +796,7 @@ export {
type FocusManifestPublicStatsConfig,
type FocusManifestDraftFlowConfig,
type FocusManifestUpstreamDriftIssuesConfig,
type FocusManifestFederatedIntelligenceConfig,
type FocusManifestSettings,
type FocusManifestSource,
type LinkedIssueSatisfactionMode,
Expand Down
18 changes: 13 additions & 5 deletions src/orb/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,17 @@
// different (how often THIS instance's own PRs lose a local collision) than "identities farming wins," so no
// proxy for it is implemented — a misleading proxy would be worse than none.

const MIN_DECIDED = 5; // an instance needs at least this many decided PRs to count toward the fleet median
// Exported so the federated bundle export (#1970, src/orb/federated-bundle.ts) gates its own published
// precision on the SAME volume bar the fleet median uses — a bundle must not advertise a precision the fleet
// would refuse to count.
export const MIN_DECIDED = 5; // an instance needs at least this many decided PRs to count toward the fleet median
const OUTLIER_BAND = 0.25; // |instance precision − fleet median| beyond this flags the instance
const GAMING_VOLUME_MULTIPLIER = 2; // an instance's decided count more than this many times the fleet median
const GAMING_PRECISION_BAND = OUTLIER_BAND; // mergePrecision this far ABOVE the fleet median (one-sided)
const GAMING_REVERSAL_RATIO = 0.5; // reversalRate below this fraction of the fleet median

/** Per-instance confusion-matrix cell as stored. */
interface Cell {
export interface Cell {
instance_id: string;
verdict: string | null;
outcome: string;
Expand Down Expand Up @@ -93,7 +96,7 @@ function median(xs: number[]): number | null {
return s.length % 2 === 0 ? (s[mid - 1]! + s[mid]!) / 2 : s[mid]!;
}

function percentile(sorted: number[], p: number): number | null {
export function percentile(sorted: number[], p: number): number | null {
if (sorted.length === 0) return null;
// Nearest-rank: the p-th percentile is the value at 1-based rank ceil(p/100 * N), i.e. index
// ceil(p/100 * N) - 1. `Math.floor(p/100 * N)` overshot by one rank whenever p/100 * N was an
Expand All @@ -104,8 +107,13 @@ function percentile(sorted: number[], p: number): number | null {
}

/** Fold the confusion-matrix cells for one instance into accuracy metrics (reversals count as the gate
* being wrong: a reverted merge is a false positive; a reopened close is a false negative). */
function foldInstance(instanceId: string, cells: Cell[]): InstanceMetrics {
* being wrong: a reverted merge is a false positive; a reopened close is a false negative).
*
* Exported for the federated bundle export (#1970, src/orb/federated-bundle.ts): a bundle publishes this
* instance's own precision for #6481 to compare against the peer median computed here, so both sides MUST use
* this one definition — reimplementing it there would silently make the comparison apples-to-oranges. Callers
* must pass a non-empty `cells` (reversalRate divides by the decided total). */
export function foldInstance(instanceId: string, cells: Cell[]): InstanceMetrics {
let wouldMerge = 0, mergeConfirmed = 0, mergeFalse = 0;
let wouldClose = 0, closeConfirmed = 0, closeFalse = 0;
let reversals = 0, decided = 0;
Expand Down
Loading
Loading