Skip to content

refactor(content-lane): remove metagraphed-specific hardcoding from the registry-review engine - #2443

Merged
JSONbored merged 7 commits into
mainfrom
claude/pr-b-content-lane-genericity
Jul 2, 2026
Merged

refactor(content-lane): remove metagraphed-specific hardcoding from the registry-review engine#2443
JSONbored merged 7 commits into
mainfrom
claude/pr-b-content-lane-genericity

Conversation

@JSONbored

@JSONbored JSONbored commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

gittensory is meant to be installed by any self-hosted repo maintainer, not just JSONbored. The content-lane registry-review subsystem was built for and tested against exactly one customer (JSONbored/metagraphed) and its own header comments already promised genericity ("metagraphed is just the FIRST spec... a spec can later be loaded from per-repo .gittensory.yml config so a new registry needs config, not a code change"). This PR closes that gap:

Why one PR for three issues: these were designed and built as one continuous, tightly-coupled sequence (each step's tests/wiring build directly on the previous), and splitting them further after the fact would require redundant rework without additional review value — the same reviewer needs the full arc to evaluate any one piece. Commits reference all three issue numbers.

Security fix included: an adversarial-review pass on this change found that the shared glob-to-RegExp compiler (change-guardrail.ts's globToRegExp, newly exported here for reuse) is exponential-time on adversarial chained-wildcard patterns — empirically verified ~19 seconds at 5 chained wildcards against a 300-char input. The new contentLane.*Glob config fields cap wildcard count at parse time (normalizeOptionalGlob, MAX_GLOB_WILDCARDS = 3, empirically verified safe), rejecting an over-complex glob before it ever reaches RegExp compilation. This fix is scoped to the new config surface only — hardening the pre-existing hardGuardrailGlobs consumer of the same function is a separate follow-up PR (different, safety-critical fail-direction; see that PR for details).

Maintainability fix included: an unregistered contentLane.validatorId (an operator typo, e.g. "metagraph" instead of "metagraphed") previously degraded silently to structural-only gating — indistinguishable from a deliberate no-validator config. spec-resolver.ts now exposes unregisteredValidatorId/registeredValidatorIds, and evaluateWithSurfaceLane pushes a non-blocking advisory finding naming the bad id and the known registered ids directly into the PR comment. Also switched the REGISTRY_VALIDATORS lookup to Object.hasOwn (was bracket-truthiness) so a validatorId matching an inherited Object.prototype key (e.g. "toString") is correctly reported as unregistered.

Scope

Validation

  • git diff --check
  • npm run actionlint (via npm run test:ci)
  • npm run typecheck
  • npm run test:coverage locally — 100% statements/branches/functions/lines on every changed line in every touched file
  • npm run test:workers (via npm run test:ci)
  • npm run build:mcp (via npm run test:ci)
  • npm run test:mcp-pack (via npm run test:ci)
  • npm run ui:openapi:check (via npm run test:ci) — no API/schema changes
  • npm run ui:lint (via npm run test:ci)
  • npm run ui:typecheck (via npm run test:ci)
  • npm run ui:build (via npm run test:ci)
  • npm audit --audit-level=moderate — 0 vulnerabilities
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries — includes a synthetic-second-repo end-to-end test proving config-only activation, a dedicated ReDoS regression test proving the pathological glob is rejected before ever reaching RegExp compilation, and tests proving an unregistered validatorId surfaces a diagnostic finding (including an Object.prototype-key edge case).

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests. — N/A, no such changes.
  • API/OpenAPI/MCP behavior is updated and tested where needed. — N/A, no API-surface change (this is .gittensory.yml config-as-code, not a REST/MCP surface).
  • UI changes use live API data or real empty/error/loading states. — N/A, no UI change.
  • Visible UI changes include a UI Evidence section. — N/A, backend-only change, no UI Evidence needed.
  • Public docs/changelogs are updated where needed. — N/A, no changelog edit.

Notes

  • No DB migration: contentLane follows the same no-DB precedence pattern as the other converged features (rag/reputation/unifiedComment/safety), not the DB-backed settings:/gate: shape — it has no dashboard UI today and doesn't need one for this change.
  • Semantic (domain-specific) validation stays a deliberate, bounded, one-time code contribution (a new validator module + one registration in REGISTRY_VALIDATORS) rather than an attempt at a fully generic rules engine — matching the boundary the repo owner set: shared code stays generic, repo-specific richness belongs in each maintainer's own config/skills surface.
  • fix(review): allow multi-entry surface submissions and detect duplicates #2442 (this PR's original dependency) has merged into main; this branch was rebased directly onto main to drop the now-redundant duplicate commit, so the PR base retargeted automatically and the diff is scoped to just this PR's own changes.

@dosubot dosubot Bot added the size:L label Jul 2, 2026
@loopover-orb

loopover-orb Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Warning

🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨

⏸️ Gittensory review result - manual review recommended

Review updated: 2026-07-02 01:58:01 UTC

13 files · 1 AI reviewer · 1 blocker · readiness 75/100 · CI green · clean

⏸️ Suggested Action - Manual Review

  • AI reviewers agree on a likely critical defect: src/review/content-lane-wire.ts:evaluateWithSurfaceLane only sets manifestLoadFailed when loadRepoFocusManifest throws, but the production loader is documented here as degrading fetch/parse failures to an empty manifest, so a non-allowlisted repo with a configured contentLane can hit a transient manifest read failure, resolve no spec at line 256, and return the generic gate at line 259 without running the registry lane. — Resolve the flagged defect, or override if the AI reviewers are mistaken, then re-run the gate.

Review summary
The refactor moves registry lane activation from a hardcoded allowlist default to a manifest-resolved RegistryLaneSpec and correctly threads spec-owned validators through the orchestrator for normal configured and allowlisted paths. The key defect is the new fail-closed handling for non-allowlisted repos depends on the manifest loader throwing, while the production loader can collapse read/parse failures into an empty manifest, so the new self-hosted config path can still be silently skipped. The wildcard-group guard is directionally sound; a globstar plus a filename star is two wildcard groups, not three.

Blockers

  • src/review/content-lane-wire.ts:evaluateWithSurfaceLane only sets manifestLoadFailed when loadRepoFocusManifest throws, but the production loader is documented here as degrading fetch/parse failures to an empty manifest, so a non-allowlisted repo with a configured contentLane can hit a transient manifest read failure, resolve no spec at line 256, and return the generic gate at line 259 without running the registry lane.
Nits — 6 non-blocking
  • src/review/content-lane-wire.ts:57 should sanitize or strictly quote-limit the manifest-sourced validatorId before putting it into public advisory text.
  • src/review/content-lane-wire.ts:244 adds the unknown-validator advisory before knowing whether the PR touches the registry lane, which can make unrelated PR comments carry content-lane configuration diagnostics.
  • src/signals/focus-manifest.ts:625 should verify overlong glob rejection happens before any shared string normalizer truncates the value, because the comment promises rejection rather than compiling a shortened pattern.
  • Change loadRepoFocusManifest or its caller to return a distinct read-status result so evaluateWithSurfaceLane can distinguish “no manifest configured” from “manifest read failed” on the production path.
  • Add a production-path regression test for a non-allowlisted repo where the real manifest-loading layer returns an empty fallback after a read failure, not just a throwing test override.
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.

Concerns raised — review before merging

  • src/review/content-lane-wire.ts:evaluateWithSurfaceLane only sets manifestLoadFailed when loadRepoFocusManifest throws, but the production loader is documented here as degrading fetch/parse failures to an empty manifest, so a non-allowlisted repo with a configured contentLane can hit a transient manifest read failure, resolve no spec at line 256, and return the generic gate at line 259 without running the registry lane.
Signal Result Evidence
Code review ❌ 1 blocker 1 reviewer
Linked issue ✅ Linked #2433, #2434, #2435
Related work ⚠️ 3 scoped overlaps Top overlaps are listed below; lower-confidence bulk is hidden.
Change scope ❌ 8/20 High review scope from cached public metadata (size label size:XL; 3 linked issues).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 65 registered-repo PR(s), 55 merged, 563 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 65 PR(s), 563 issue(s).
Gate result ❌ Blocking Repo-configured hard blocker found.
Review context
Contributor next steps
  • Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Review top overlaps.
  • Add a concise scope and risk note.
  • Triage stale or unlinked PRs.
  • No action.
  • Check active issues and PRs before submitting.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.

  • Re-run Gittensory review

@loopover-orb loopover-orb Bot added gittensor gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. labels Jul 2, 2026
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.93%. Comparing base (38647fc) to head (b29b80b).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2443      +/-   ##
==========================================
+ Coverage   95.91%   95.93%   +0.01%     
==========================================
  Files         224      225       +1     
  Lines       25235    25324      +89     
  Branches     9177     9214      +37     
==========================================
+ Hits        24205    24294      +89     
  Misses        417      417              
  Partials      613      613              
Files with missing lines Coverage Δ
src/review/content-lane-wire.ts 98.43% <100.00%> (+0.47%) ⬆️
src/review/content-lane/orchestrator.ts 100.00% <100.00%> (ø)
src/review/content-lane/registry-logic.ts 100.00% <ø> (ø)
src/review/content-lane/spec-resolver.ts 100.00% <100.00%> (ø)
src/signals/change-guardrail.ts 100.00% <100.00%> (ø)
src/signals/focus-manifest-loader.ts 99.09% <ø> (ø)
src/signals/focus-manifest.ts 99.24% <100.00%> (+0.04%) ⬆️
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Base automatically changed from claude/pr-a-multi-entry-dedup to main July 2, 2026 00:41
@dosubot dosubot Bot added size:XL and removed size:L labels Jul 2, 2026
@JSONbored JSONbored self-assigned this Jul 2, 2026
JSONbored added 2 commits July 1, 2026 17:58
…he registry-review engine

gittensory is meant to be installed by any self-hosted repo maintainer, not
just JSONbored/metagraphed. The RegistryLaneSpec abstraction was already
generic, but nothing let a second maintainer actually reach it without
editing gittensory's own TypeScript source and redeploying.

- Rename runMetagraphedSurfaceGate to runRegistrySurfaceGate; stop
  re-exporting netuid-verification.ts's Bittensor-only helpers from the
  generic content-lane barrel (closes #2433).
- Add assessAppendedEntry/assessProviderEntry callback fields to
  RegistryLaneSpec; the orchestrator calls the spec-supplied validators
  instead of hardcoded imports, so a different registry can supply its own
  domain validator without touching shared engine code (closes #2434).
- Add a contentLane: block to .gittensory.yml (FocusManifestContentLaneConfig)
  and a resolveRegistryLaneSpec resolver mirroring resolveConvergedFeature's
  precedence (env kill-switch -> per-repo config -> allowlist default), so a
  second maintainer's registry repo can activate the deterministic surface
  lane purely from their own config, with today's zero-config behavior for
  metagraphed unchanged (closes #2435).
- Glob fields (entryFileGlob/providerFileGlob/artifactGlob) are capped at
  parse time to a safe wildcard count: an adversarial-review pass on this
  change found the shared glob-to-RegExp compiler is exponential-time on
  chained wildcards (empirically ~19s at 5 chained wildcards), so the new
  config surface rejects an over-complex glob before it ever reaches RegExp
  compilation.
buildRegistryLaneSpecFromConfig already degraded an unregistered
validatorId to structural-only gating silently (a legitimate mode for
a registry with no validator yet), which made an operator typo (e.g.
"metagraph" instead of "metagraphed") indistinguishable from a
deliberate choice — no signal reached the maintainer.

Add unregisteredValidatorId()/registeredValidatorIds() to
spec-resolver.ts and surface a non-blocking advisory finding from
evaluateWithSurfaceLane so a bad validatorId shows up directly in the
PR comment, naming the offending id and the known registered ids.

Also switch the REGISTRY_VALIDATORS lookup to Object.hasOwn instead of
bracket-truthiness, so a validatorId matching an inherited
Object.prototype key (e.g. "toString") is correctly reported as
unregistered rather than silently matching a prototype method.
@JSONbored
JSONbored force-pushed the claude/pr-b-content-lane-genericity branch from 23e3fcd to 2ca08ee Compare July 2, 2026 01:00
@dosubot dosubot Bot added size:L and removed size:XL labels Jul 2, 2026
JSONbored added 3 commits July 1, 2026 18:33
…er-long globs

Two gaps flagged in evaluateWithSurfaceLane and normalizeOptionalGlob:

- A non-allowlisted repo's ONLY way to configure a registry content
  lane is its own .gittensory.yml. A manifest-load failure was caught
  as null and silently treated the same as "no contentLane configured"
  -- letting a registry-submission PR merge unevaluated on nothing
  more than a transient read blip. Now holds the gate neutral in that
  specific case (never overriding a real generic hard blocker, which
  is always preserved).

- An over-long contentLane glob (entryFileGlob/providerFileGlob/
  artifactGlob) was truncated to MAX_ITEM_LENGTH and still returned,
  silently compiling a DIFFERENT file-scope pattern than configured.
  Now rejected outright, matching the function's own doc comment and
  the established pattern used elsewhere in this file.
* fix(signals): cap globToRegExp wildcard count to prevent ReDoS

hardGuardrailGlobs (src/review/guardrail-config.ts) and any future
maintainer-supplied glob compiled via globToRegExp are vulnerable to
catastrophic backtracking on chained `*` wildcards: 5 chained
wildcards against a 300-char adversarial path took ~19 seconds.

Cap wildcard count at compile time (MAX_GLOB_WILDCARDS = 6, well
above any of the ~10 real guardrail globs today). An over-complex
glob is treated as matching every path rather than failing open,
since a guardrail's job is to force manual review on uncertainty —
mirroring isGuardrailHit's existing "unknown ⇒ treat as a hit"
fail-safe direction.

* fix(signals): bake the ReDoS wildcard cap into globToRegExp itself

The wildcard-count guard previously lived only in matchesAny's
wrapper, so any other direct caller of the exported globToRegExp
(e.g. content-lane/spec-resolver.ts) could still compile and .test()
a pathological glob and hit catastrophic backtracking — the exact
gap this PR set out to close.

globToRegExp now short-circuits an over-complex glob to a
never-matching sentinel regex instead of compiling it, so every
caller (present or future, direct or indirect) is protected
automatically. "Never matches" (not "matches everything") is the
correct default for the general-purpose compiler, since a false
"matches everything" would misclassify unrelated files for a
non-guardrail caller; matchesAny keeps its own override to the
opposite fail direction for guardrail semantics specifically.

* fix(signals): count wildcard GROUPS, not raw * characters, for the ReDoS cap

The flagged blocker was correct: MAX_GLOB_WILDCARDS=6 let the PR's own
motivating example (5 chained wildcards, empirically catastrophic) through
uncapped. Lowering the raw-character cap to 2 fixed that but broke a real
consumer -- content-lane/spec-resolver.ts's artifactGlob "public/**/*.json"
has 3 raw '*' characters (** counts as two) despite being empirically
instant even against a 4,000-char adversarial path, because a `**`
globstar compiles to a single .* group, not two independent wildcards.

Re-benchmarked with the right unit (wildcard GROUPS, where a ** pair is
one group): 2 groups stays sub-second even at 32,000 adversarial chars; 3
groups is already dangerous (over 2s at ~4,000 chars for a chained-*
shape, over 100ms at ~1,600 chars for a chained-** shape); 4+ groups is
catastrophic (35s at 1,614 chars for 4 chained ** groups). Caps
MAX_GLOB_WILDCARD_GROUPS at 2 -- the highest value proven safe -- via a
new countWildcardGroups that mirrors globToRegExp's own tokenization
(consuming a ** pair, and its trailing /, as one group), so both the
original flagged case and real 2-group globs like public/**/*.json are
handled correctly.
@dosubot dosubot Bot added size:XL and removed size:L labels Jul 2, 2026
JSONbored added 2 commits July 1, 2026 18:49
…arsing and compilation

normalizeOptionalGlob (focus-manifest.ts) capped contentLane globs at
3 raw '*' characters, but globToRegExp (change-guardrail.ts) rejects
any glob with more than 2 wildcard GROUPS (a '**' pair counts as one
group, not two) by compiling it to NEVER_MATCHES. A glob like
"a*b*c*.json" (3 groups, no '**' pairs) was accepted as configured but
silently could never match any file once compiled -- a content lane
that looks active but never fires.

Export hasUnsafeWildcardCount from change-guardrail.ts and reuse it
directly in normalizeOptionalGlob instead of an independently-counted
threshold, so the parser and compiler can never drift apart again.
@JSONbored
JSONbored merged commit d464668 into main Jul 2, 2026
12 checks passed
@JSONbored
JSONbored deleted the claude/pr-b-content-lane-genericity branch July 2, 2026 02:27
@github-project-automation github-project-automation Bot moved this from Todo to Done in gittensory - v1 roadmap Jul 2, 2026
@github-actions github-actions Bot mentioned this pull request Jul 2, 2026
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

No open projects
Status: Done

1 participant