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
12 changes: 12 additions & 0 deletions .gittensory.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -1123,6 +1123,18 @@ settings:
# screenshots: false
# improvementSignal: false

# Optional ecosystem/network PLUGINS -- distinct from `features:` above, which only toggles gittensory's own
# converged review capabilities. Each key here couples this instance to an external system and is OFF unless
# BOTH a deployment-wide GITTENSORY_EXPERIMENTAL_* env kill-switch AND an explicit per-repo `true` are set (no
# GITTENSORY_REVIEW_REPOS allowlist fallback -- unlike `features:`, there is no default-on path). `gittensor`
# is the first plugin: gittensory's original subnet mining-registry/scoring integration (per-repo emission
# share, maintainer cut, label multipliers pulled from the gittensor subnet's registry), now opt-in rather
# than a core dependency -- a self-host instance that never sets this has zero footprint from it: no fetch
# from the gittensor subnet registry, no local tracking/backfill of other repos on that subnet. Future
# plugins land in this same block as gittensory broadens beyond gittensor.
# experimental:
# gittensor: true

# Registry-review lane (#2435): lets a self-hosted maintainer point gittensory at their OWN structured
# registry (e.g. a subnet/plugin/package catalog) without a gittensory code change -- reviewing additions
# to a data file the same way it reviews code. Uncomment and set at least entryFileGlob + collectionField
Expand Down
12 changes: 12 additions & 0 deletions config/examples/gittensory.full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,18 @@ settings:
# screenshots: false
# improvementSignal: false

# Optional ecosystem/network PLUGINS -- distinct from `features:` above, which only toggles gittensory's own
# converged review capabilities. Each key here couples this instance to an external system and is OFF unless
# BOTH a deployment-wide GITTENSORY_EXPERIMENTAL_* env kill-switch AND an explicit per-repo `true` are set (no
# GITTENSORY_REVIEW_REPOS allowlist fallback -- unlike `features:`, there is no default-on path). `gittensor`
# is the first plugin: gittensory's original subnet mining-registry/scoring integration (per-repo emission
# share, maintainer cut, label multipliers pulled from the gittensor subnet's registry), now opt-in rather
# than a core dependency -- a self-host instance that never sets this has zero footprint from it: no fetch
# from the gittensor subnet registry, no local tracking/backfill of other repos on that subnet. Future
# plugins land in this same block as gittensory broadens beyond gittensor.
# experimental:
# gittensor: true

# Registry-review lane (#2435): lets a self-hosted maintainer point gittensory at their OWN structured
# registry (e.g. a subnet/plugin/package catalog) without a gittensory code change -- reviewing additions
# to a data file the same way it reviews code. Uncomment and set at least entryFileGlob + collectionField
Expand Down
58 changes: 58 additions & 0 deletions packages/gittensory-engine/src/focus-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,21 @@ export type ConvergedFeatureKey = (typeof CONVERGED_FEATURE_KEYS)[number];
* `GITTENSORY_REVIEW_REPOS` allowlist default, so an operator who sets nothing keeps today's behavior. */
export type FocusManifestFeaturesConfig = { present: boolean } & Record<ConvergedFeatureKey, boolean | null>;

/** Optional ecosystem/network integrations under the `experimental:` block — plugins that couple gittensory to
* an external system rather than core review behavior. Starts with `gittensor` (the subnet mining-registry/
* scoring integration gittensory originally shipped with); future plugins land in this same array as the
* product broadens beyond gittensor. Deliberately a SEPARATE block from `features:` (converged review
* capabilities) — an operator (especially self-host) should be able to see at a glance which toggles are "how
* gittensory reviews PRs" vs "which external network/ecosystem this instance opts into." */
export const EXPERIMENTAL_PLUGIN_KEYS = ["gittensor"] as const;
export type ExperimentalPluginKey = (typeof EXPERIMENTAL_PLUGIN_KEYS)[number];

/** Per-repo activation for `experimental:` plugins. Unlike `features:`, there is no `GITTENSORY_REVIEW_REPOS`
* allowlist fallback for ANY key here — every plugin is the "manifestOnly" precedence shape
* (`resolveManifestOnlyFeature`): OFF unless the operator's global env kill-switch AND an explicit per-repo
* `true` are BOTH set. So an instance that never opts in has zero footprint from any experimental plugin. */
export type FocusManifestExperimentalConfig = { present: boolean } & Record<ExperimentalPluginKey, boolean | null>;

/**
* Per-repo registry-review lane configuration (`contentLane:` block, #2435) — lets a self-hosted maintainer
* configure their OWN registry (structural file-scope patterns + entry-count cap + dedup fields) without a
Expand Down Expand Up @@ -882,6 +897,7 @@ export type FocusManifest = {
settings: FocusManifestSettings;
review: FocusManifestReviewConfig;
features: FocusManifestFeaturesConfig;
experimental: FocusManifestExperimentalConfig;
contentLane: FocusManifestContentLaneConfig;
repoDocGeneration: FocusManifestRepoDocGenerationConfig;
reviewRecap: FocusManifestReviewRecapConfig;
Expand Down Expand Up @@ -981,6 +997,11 @@ const EMPTY_FEATURES_CONFIG: FocusManifestFeaturesConfig = {
improvementSignal: null,
};

const EMPTY_EXPERIMENTAL_CONFIG: FocusManifestExperimentalConfig = {
present: false,
gittensor: null,
};

const EMPTY_CONTENT_LANE_CONFIG: FocusManifestContentLaneConfig = {
present: false,
entryFileGlob: null,
Expand Down Expand Up @@ -1034,6 +1055,7 @@ const EMPTY_MANIFEST: FocusManifest = {
settings: {},
review: { present: false, footerText: null, note: null, fields: {}, enrichmentAnalyzers: {}, profile: null, tone: null, securityFocus: null, inlineComments: null, fixHandoff: null, autoMergeSummary: null, suggestions: null, changedFilesSummary: null, effortScore: null, impactMap: null, cultureProfile: null, selftune: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { ...EMPTY_MAX_FINDINGS_CONFIG }, commentVerbosity: null, e2eTestDelivery: null, e2eTestAutoTrigger: null, pathInstructions: [], instructions: null, excludePaths: [], pathFilters: [], preMergeChecks: [], autoReview: { ...EMPTY_AUTO_REVIEW_CONFIG }, aiModel: { ...EMPTY_SELF_HOST_AI_MODEL_CONFIG }, visual: { ...EMPTY_VISUAL_CONFIG }, linkedIssueSatisfaction: null, sharedConfigSource: null },
features: { ...EMPTY_FEATURES_CONFIG },
experimental: { ...EMPTY_EXPERIMENTAL_CONFIG },
contentLane: { ...EMPTY_CONTENT_LANE_CONFIG },
repoDocGeneration: { ...EMPTY_REPO_DOC_GENERATION_CONFIG },
reviewRecap: { ...EMPTY_REVIEW_RECAP_CONFIG },
Expand Down Expand Up @@ -1065,6 +1087,7 @@ function emptyManifest(source: FocusManifestSource, warnings: string[] = []): Fo
settings: {},
review: { present: false, footerText: null, note: null, fields: {}, enrichmentAnalyzers: {}, profile: null, tone: null, securityFocus: null, inlineComments: null, fixHandoff: null, autoMergeSummary: null, suggestions: null, changedFilesSummary: null, effortScore: null, impactMap: null, cultureProfile: null, selftune: null, reviewMemory: null, findingCategories: null, inlineCommentsPerCategory: null, minFindingSeverity: null, maxFindings: { ...EMPTY_MAX_FINDINGS_CONFIG }, commentVerbosity: null, e2eTestDelivery: null, e2eTestAutoTrigger: null, pathInstructions: [], instructions: null, excludePaths: [], pathFilters: [], preMergeChecks: [], autoReview: { ...EMPTY_AUTO_REVIEW_CONFIG }, aiModel: { ...EMPTY_SELF_HOST_AI_MODEL_CONFIG }, visual: { ...EMPTY_VISUAL_CONFIG }, linkedIssueSatisfaction: null, sharedConfigSource: null },
features: { ...EMPTY_FEATURES_CONFIG },
experimental: { ...EMPTY_EXPERIMENTAL_CONFIG },
contentLane: { ...EMPTY_CONTENT_LANE_CONFIG },
repoDocGeneration: { ...EMPTY_REPO_DOC_GENERATION_CONFIG },
reviewRecap: { ...EMPTY_REVIEW_RECAP_CONFIG },
Expand Down Expand Up @@ -1492,6 +1515,39 @@ export function featuresConfigToJson(features: FocusManifestFeaturesConfig): Jso
return out;
}

/**
* Parse the optional `experimental:` mapping — per-repo activation for optional ecosystem/network plugins
* (starting with `gittensor`, the subnet mining/scoring integration). Mirrors parseFeaturesConfig's shape and
* validation; kept as a SEPARATE top-level block from `features:` so plugin integrations that couple gittensory
* to an external network stay visibly distinct from the converged REVIEW capabilities `features:` toggles, and
* so future plugins land in the same place without touching `features:`'s semantics.
*/
function parseExperimentalConfig(value: JsonValue | undefined, warnings: string[]): FocusManifestExperimentalConfig {
const experimental: FocusManifestExperimentalConfig = { ...EMPTY_EXPERIMENTAL_CONFIG };
if (value === undefined || value === null) return experimental;
if (typeof value !== "object" || Array.isArray(value)) {
warnings.push('Manifest "experimental" must be a mapping; ignoring it.');
return experimental;
}
const record = value as Record<string, JsonValue>;
for (const key of EXPERIMENTAL_PLUGIN_KEYS) {
experimental[key] = normalizeOptionalBoolean(record[key], `experimental.${key}`, warnings);
}
experimental.present = EXPERIMENTAL_PLUGIN_KEYS.some((key) => experimental[key] !== null);
return experimental;
}

/** Serialize an experimental config back into the parse-compatible `experimental:` shape so a cached snapshot
* round-trips through {@link parseExperimentalConfig} unchanged. Returns null when nothing is configured. */
export function experimentalConfigToJson(experimental: FocusManifestExperimentalConfig): JsonValue {
if (!experimental.present) return null;
const out: Record<string, JsonValue> = {};
for (const key of EXPERIMENTAL_PLUGIN_KEYS) {
if (experimental[key] !== null) out[key] = experimental[key];
}
return out;
}

/** A positive INTEGER count (not a score/confidence) — e.g. `contentLane.maxAppendedEntries` counts discrete
* surfaces[] entries, so a fractional value (a likely typo) would render a nonsensical contributor-facing close
* message ("append between 1 and 2.5 entries"). Rejects fractional and non-positive values alike. */
Expand Down Expand Up @@ -3025,6 +3081,7 @@ export function parseFocusManifest(raw: unknown, source?: FocusManifestSource):
settings: parseSettingsOverride(record.settings, warnings, resolvedSource),
review: parseReviewConfig(record.review, warnings),
features: parseFeaturesConfig(record.features, warnings),
experimental: parseExperimentalConfig(record.experimental, warnings),
contentLane: parseContentLaneConfig(record.contentLane, warnings),
repoDocGeneration: parseRepoDocGenerationConfig(record.repoDocGeneration, warnings),
reviewRecap: parseReviewRecapConfig(record.reviewRecap, warnings),
Expand All @@ -3043,6 +3100,7 @@ export function parseFocusManifest(raw: unknown, source?: FocusManifestSource):
Object.keys(manifest.settings).length === 0 &&
!manifest.review.present &&
!manifest.features.present &&
!manifest.experimental.present &&
!manifest.contentLane.present &&
!manifest.repoDocGeneration.present &&
!manifest.reviewRecap.present &&
Expand Down
4 changes: 4 additions & 0 deletions packages/gittensory-engine/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,7 @@ export {
export {
compileFocusManifestPolicy,
contentLaneConfigToJson,
experimentalConfigToJson,
featuresConfigToJson,
formatManifestValidationNotice,
gateConfigToJson,
Expand All @@ -507,6 +508,7 @@ export {
settingsOverrideToJson,
MAX_FOCUS_MANIFEST_BYTES,
CONVERGED_FEATURE_KEYS,
EXPERIMENTAL_PLUGIN_KEYS,
COMMENT_VERBOSITY_LEVELS,
EMPTY_AUTO_REVIEW_CONFIG,
EMPTY_MAX_FINDINGS_CONFIG,
Expand All @@ -519,8 +521,10 @@ export {
type AutoReviewConfig,
type CommentVerbosity,
type ConvergedFeatureKey,
type ExperimentalPluginKey,
type FocusManifest,
type FocusManifestContentLaneConfig,
type FocusManifestExperimentalConfig,
type FocusManifestFeaturesConfig,
type FocusManifestGateConfig,
type FocusManifestIssueDiscoveryPolicy,
Expand Down
8 changes: 8 additions & 0 deletions src/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,14 @@ declare global {
* recording are wired, reading a promoted override into the live gate is a noted follow-up that must not
* risk loosening the gate. See src/review/selftune-wire.ts. */
GITTENSORY_REVIEW_SELFTUNE?: string;
/** Experimental `gittensor` plugin (the `experimental:` manifest block, first key): the operator-level
* kill-switch for gittensory's original subnet mining-registry/scoring integration, now opt-in rather than
* a core dependency. ANDed with the per-repo `.gittensory.yml experimental.gittensor` opt-in -- neither
* alone is sufficient, and unlike `features:` there is no GITTENSORY_REVIEW_REPOS allowlist fallback.
* Default OFF -- flag-OFF (or every repo unset), refresh-registry is never enqueued (see src/index.ts) and
* a self-host box makes zero outbound contact with the gittensor subnet registry. See
* src/review/gittensor-wire.ts. */
GITTENSORY_EXPERIMENTAL_GITTENSOR?: string;
/** Maintainer recap digest (#1963, #2248): when truthy, a cross-repo RecapReport -- gittensory's OWN
* gate-precision + outcome-calibration data folded across every scanned repo (buildMaintainerRecap,
* #2239) -- is delivered to Discord on a cron cadence. GITTENSORY_RECAP_CADENCE ("daily" | "weekly",
Expand Down
15 changes: 14 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { processDlqBatch } from "./queue/dlq";
import { processJob } from "./queue/processors";
import { isOrbBrokerEnabled } from "./orb/broker";
import { isOrbBrokerMode } from "./orb/broker-client";
import { gittensorEnabledRepoFullNames } from "./review/gittensor-wire";
import { isOpsEnabled } from "./review/ops-wire";
import { isRecapEnabled, resolveMaintainerRecapManifestOverride, shouldFireMaintainerRecap } from "./review/maintainer-recap-wire";
import { isSweepWatchdogEnabled } from "./review/sweep-watchdog";
Expand Down Expand Up @@ -202,7 +203,19 @@ async function enqueueScheduledJobs(env: Env, controller: ScheduledController):
// ZERO new work and the enqueued set is byte-identical to today.
if (selfHostedReviews && isReconciliationWindow && isPrReconciliationEnabled(env)) jobs.push({ type: "reconcile-open-prs", requestedBy: "schedule" });
if (isHourly) {
jobs.push({ type: "refresh-registry", requestedBy: "schedule" });
// Isolation (#experimental-gittensor-plugin): on self-host, refresh-registry both FETCHES from and
// PERSISTS the whole upstream gittensor-subnet registry (entrius/gittensor has no server-side filtering,
// and persistRegistrySnapshot's own self-host scoping — see registry/sync.ts — narrows what gets WRITTEN
// locally but can't narrow what gets fetched). Skip enqueuing the job entirely when this instance has no
// repo opted into the experimental `gittensor` plugin, so a plain self-host box makes ZERO outbound
// contact with the subnet registry. Cloud is unaffected — always enqueues, exactly like before this
// narrowing existed.
const gittensorOptedIn = selfHostedReviews ? await gittensorEnabledRepoFullNames(env) : null;
if (!selfHostedReviews || (gittensorOptedIn && gittensorOptedIn.size > 0)) {
jobs.push({ type: "refresh-registry", requestedBy: "schedule" });
} else {
console.log(JSON.stringify({ event: "refresh_registry_skipped_no_gittensor_opt_in" }));
}
// Brokered self-host installed-repo sync (#5028): the central Orb relay deliberately does not forward
// installation/installation_repositories events to brokered containers, so a brokered self-host has no
// other way to learn its own repo list beyond the first forwarded PR/issue event per repo. Self-host +
Expand Down
59 changes: 59 additions & 0 deletions src/review/gittensor-wire.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Gittensor experimental-plugin activation wiring. `gittensor` is the first key under the `experimental:`
// manifest block (EXPERIMENTAL_PLUGIN_KEYS) -- gittensory's original subnet mining-registry/scoring
// integration, now an OPT-IN plugin rather than a core dependency, so a self-host instance with no gittensor
// affiliation has zero footprint from it (see registry/sync.ts's self-host scoping, which this feeds, and
// index.ts's cron gate, which skips the registry fetch entirely when nothing is opted in).
//
// Mirrors impact-map-wire.ts's isImpactMapEnabled/shouldComputeImpactMap: a single GLOBAL env kill-switch the
// operator controls, ANDed with an EXPLICIT per-repo `.gittensory.yml experimental.gittensor` manifest `true`
// (resolveManifestOnlyFeature's shape -- no allowlist fallback, unlike the converged `features:` block). Both
// OFF by default: with the env flag unset, no repo is ever treated as gittensor-opted-in. Cloud never consults
// any of this -- registry/sync.ts only calls gittensorEnabledRepoFullNames on the self-host branch, so the
// hosted product's existing full-subnet behavior is untouched.

import { listRepositories } from "../db/repositories";
import { loadRepoFocusManifest } from "../signals/focus-manifest-loader";
import { resolveManifestOnlyFeature } from "./feature-activation";

/** True when the gittensor subnet-scoring plugin is enabled at the operator level. Flag-OFF (default) -> no
* repo is ever gittensor-opted-in regardless of what any `.gittensory.yml` says, and
* {@link gittensorEnabledRepoFullNames} short-circuits before reading a single manifest. Truthy follows the
* codebase convention (`/^(1|true|yes|on)$/i`, same as isImpactMapEnabled / isSelfTuneEnabled). */
export function isGittensorPluginEnabled(env: { GITTENSORY_EXPERIMENTAL_GITTENSOR?: string | undefined }): boolean {
return /^(1|true|yes|on)$/i.test(env.GITTENSORY_EXPERIMENTAL_GITTENSOR ?? "");
}

/** Resolve whether the gittensor plugin is active for THIS repo: the operator's global env kill-switch AND an
* explicit per-repo manifest opt-in. Neither alone is sufficient -- mirrors every other manifestOnly feature
* gate in this codebase (env kill-switch first, then the manifest narrows it further). */
export function shouldEnableGittensorForRepo(
env: { GITTENSORY_EXPERIMENTAL_GITTENSOR?: string | undefined },
manifestGittensorEnabled: boolean | null | undefined,
): boolean {
return resolveManifestOnlyFeature(isGittensorPluginEnabled(env), manifestGittensorEnabled);
}

/**
* The set of repos (lowercased full names) this self-host instance has opted into the gittensor plugin for --
* every locally-known repo (listRepositories, deliberately NOT filtered by isRegistered: isRegistered is
* itself DOWNSTREAM of this decision on self-host, see registry/sync.ts's persistRegistrySnapshot, so filtering
* on it here would be circular -- a repo could never earn its first isRegistered=true) whose manifest sets
* `experimental.gittensor: true` AND the global env kill-switch is on. A per-repo manifest-load error is
* skipped (treated as not-opted-in), never aborts the pass -- mirrors selftune-wire.ts's selfTuneRepos.
* Flag-OFF (default) short-circuits before listing repos or loading a single manifest, so a plain self-host
* instance makes zero local reads for this and zero outbound gittensor-registry requests (see index.ts).
*/
export async function gittensorEnabledRepoFullNames(env: Env & { GITTENSORY_EXPERIMENTAL_GITTENSOR?: string | undefined }): Promise<Set<string>> {
if (!isGittensorPluginEnabled(env)) return new Set();
const repos = await listRepositories(env);
const enabled = new Set<string>();
for (const repo of repos) {
try {
const manifest = await loadRepoFocusManifest(env, repo.fullName);
if (shouldEnableGittensorForRepo(env, manifest.experimental.gittensor)) enabled.add(repo.fullName.toLowerCase());
} catch {
/* a manifest-load blip on one repo must not block the rest of the pass */
}
}
return enabled;
}
Loading