Skip to content

feat(knowledge): filter/overlay knowledge federation engine and submission packaging - #37

Merged
fredporter merged 1 commit into
mainfrom
feat/knowledge-federation-overlay
Sep 15, 2026
Merged

fredporter merged 1 commit into
mainfrom
feat/knowledge-federation-overlay

Conversation

@fredporter

Copy link
Copy Markdown
Owner

Summary

Implements the sovereign three-tier cascading knowledge architecture specified in KNOWLEDGE_LAYERING_AND_CONTRIBUTION_SPEC.md and AGENTS.md.

  • Three-Tier Precedence: Layer 2 (Personal Sovereign ~/Vault/knowledge) > Layer 1 (Community ~/Shared/knowledge) > Layer 0 (Base Canon ~/Public/global-knowledge).
  • Non-Destructive Overlays: Local user modifications write strictly to Layer 2; Layer 0 is protected and read-only (HTTP 403 on mutation).
  • Instant Revert: Deleting/reverting an overlay restores the pristine canon baseline.
  • Automated AI Preflight Linter: Flags privacy leaks (/Users/...), credential patterns, and Prose heading standards.
  • Cryptographic Submission Bundler: Packages manifest.json, diff.patch, and provenance.json into ~/Vault/dispatches/submissions/.
  • Local Wikipedia UI Integration: Layer badges, Compare vs Canon diff modal, Show Canon toggle, Revert to Canon, Customize in Vault, and Federation Overview modal.

Tests

  • 6 backend integration tests passing (uv run pytest tests/test_knowledge_overlay.py)
  • 108 frontend tests passing (pnpm test run)
  • TypeScript type-check passing (pnpm type-check)
  • Home path policy check passing (python3 scripts/check_home_path_policy.py)

Copilot AI lite review requested due to automatic review settings September 15, 2026 14:41
@fredporter
fredporter merged commit 0a8cad4 into main Sep 15, 2026
4 checks passed
@fredporter
fredporter deleted the feat/knowledge-federation-overlay branch September 15, 2026 14:43

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical security, path-safety, provenance, and correctness issues remain.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds a three-tier knowledge federation system with overlays, APIs, submission packaging, and LocalWiki integration.

Changes:

  • Implements layered resolution, reversion, linting, and packaging.
  • Adds federation and documentation API integrations.
  • Adds reader controls, modals, badges, and tests.
File summaries
File Summary
frontend-vue/src/surfaces/documentation/LocalWikiReader.vue Federation UI controls and interactions
backend/tests/test_knowledge_overlay.py Federation integration tests
backend/app/surfaces/documentation_api.py Overlay-aware document loading
backend/app/services/knowledge_overlay.py Layer resolution and submission packaging
backend/app/api/routes.py Federation route registration
backend/app/api/federation_api.py Federation REST handlers
Review details

Suppressed comments (6)

backend/app/api/federation_api.py:106

  • The revert endpoint has the same uncaught conversion path: a malformed layer produces a 500 before the handler's try/except instead of a 400. Keep input parsing inside the guarded block, matching the overlay handler.
    layer = int(body.get("layer", 2))

backend/app/services/knowledge_overlay.py:389

  • An existing Layer 2 file that is identical to canon has canon_exists true but an empty diff_from_canon; this branch then treats it as a new document and emits a full /dev/null patch. Distinguish a genuinely new document from a no-op overlay, and reject the latter or emit an empty patch instead of packaging the whole canon file.
        diff_text = resolved["diff_from_canon"]
        if not diff_text:
            # New document without canon baseline; package full file diff

backend/app/services/knowledge_overlay.py:250

  • This delete path also follows symlinked parents: target.is_file() can succeed for a file outside the layer and target.unlink() will then delete that outside file. Validate the resolved target stays beneath the selected layer root, or use a no-follow deletion strategy, before allowing a revert.
        root = self.layer_root(layer)
        target = root / normalized

        if target.is_file():
            target.unlink()

frontend-vue/src/surfaces/documentation/LocalWikiReader.vue:235

  • Deleting only the Layer 2 file does not always restore Layer 0: when a Layer 1 file exists, revert_overlay() resolves to that community content. These new strings promise “Revert to Canon” and “restore pristine canonical baseline,” so the control misrepresents what the user will see. Rename the action to describe restoring the lower-precedence layer, or explicitly remove both upper layers.
                title="Revert personal overlay and restore pristine canonical baseline"
                @click="confirmRevertToCanon"
              >
                <UIcon name="undo" size="xs" />
                <span>{{ revertingOverlay ? 'Reverting...' : 'Revert to Canon' }}</span>

frontend-vue/src/surfaces/documentation/LocalWikiReader.vue:219

  • When toggleCanonOnly() loads canonical=true, the response replaces activeOverlay with a Layer 0 result whose is_overlaid is false. This condition then removes the only toggle, so the reader cannot return to the personal/shared content without selecting the article again. Keep this control visible while showingCanonOnly is true, or preserve the original overlay metadata.
                v-if="activeOverlay.is_overlaid && activeOverlay.canon_exists"
                type="button"
                class="wiki-btn wiki-btn--xs"
                :class="{ 'wiki-btn--active': showingCanonOnly }"
                title="Toggle between personal overlay and canonical baseline"
                @click="toggleCanonOnly"

frontend-vue/src/surfaces/documentation/LocalWikiReader.vue:448

  • This icon-only close control has no accessible name (aria-label, text, or title), so assistive technology cannot identify the button. Add a context-specific accessible label to this and the two other newly added modal close buttons.
          <button type="button" class="wiki-search-clear" @click="showDiffModal = false">
            <UIcon name="close" size="xs" />
          </button>
  • Files reviewed: 6/6 changed files
  • Comments generated: 13
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +184 to +187
app.router.add_post("/api/knowledge/federation/overlay", handle_federation_save_overlay)
app.router.add_post("/api/knowledge/federation/revert", handle_federation_revert_overlay)
app.router.add_post("/api/knowledge/federation/preflight", handle_federation_preflight)
app.router.add_post("/api/knowledge/federation/export-submission", handle_federation_export_submission)
Comment thread backend/app/api/routes.py
try:
from .federation_api import register_federation_routes

register_federation_routes(app)
Comment on lines +229 to +233
normalized = self.normalize_rel_path(rel_path)
root = self.layer_root(layer)
target = root / normalized
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(content, encoding="utf-8")
Comment on lines +428 to +430
"origin_path": resolved["effective_path"],
"canon_exists": resolved["canon_exists"],
"canon_path": resolved["canon_path"],
Comment on lines +432 to +436
"signatures": {
"local_device_fingerprint": _sha256(os.uname().nodename),
"author_id": author,
"signed_at": datetime.now(timezone.utc).isoformat(),
},
Comment on lines +780 to +785
resolved = resolver.resolve(path, prefer_canon=prefer_canon)
if resolved.get("found"):
if prefer_canon and resolved.get("canon_exists"):
canon_path_obj = Path(resolved["canon_path"]) if resolved.get("canon_path") else None
if canon_path_obj and canon_path_obj.is_file():
result["content"] = canon_path_obj.read_text(encoding="utf-8", errors="replace")
<button
type="button"
class="wiki-btn wiki-btn--xs wiki-btn--ghost"
@click="selectDocument(item.layer === 2 ? 'vault' : 'shared', item.path, item.rel_path); showFederationModal = false"
if (resp.ok) {
const data = await resp.json();
activeOverlay.value = data.overlay || activeOverlay.value;
activeSource.value = "vault";
Comment on lines +558 to +560
<div v-for="(err, i) in preflightResult.errors" :key="i" class="preflight-err-msg">
✗ {{ err }}
</div>
Comment on lines +1777 to +1781
.wiki-chip--canon {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 11px;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants