feat(core): policy-core extraction + self-host kit + fgpack workflow#5
feat(core): policy-core extraction + self-host kit + fgpack workflow#5razvantirboaca merged 2 commits intomainfrom
Conversation
- move vault resolve decision logic into shared policyCore with deterministic tests - add fail-closed vault schema validation on /api/vault/resolve - add membrane entry gate (4.5s hold) with i18n strings - add docker-compose + Makefile self-host workflow and fgpack export/verify scripts 🌬️ whisper: freedom should be runnable.
There was a problem hiding this comment.
Pull request overview
This PR extracts vault policy logic into a portable, deterministic policyCore module and adds comprehensive self-hosting infrastructure for Firegate. The changes move the system toward protocol-grade resilience by decoupling policy decisions from framework code, adding fail-closed validation, and providing a one-command self-host path with verifiable archive format (.fgpack).
Changes:
- Extracts and centralizes policy logic (gate thresholds, runtime decisions, vault validation) into shared
policyCoremodule with deterministic test coverage - Adds Membrane Gate UI component with 4.5s hold interaction and anti-frantic safeguards (keyboard accessible, i18n-aware)
- Introduces
.fgpackarchive format with export/verify scripts for lineage portability and memory continuity - Provides Docker Compose self-host kit with Makefile targets for one-command deployment and backup workflows
Reviewed changes
Copilot reviewed 19 out of 20 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| shared/src/policyCore.ts | New core module exporting vault policy logic including types, validation, entry resolution, and runtime decision computation |
| shared/src/index.js | Adds export for policyCore module to shared package |
| shared/src/tests/policyCore.test.ts | Adds deterministic tests for validation, runtime decisions, and entry resolution |
| packages/server/index.ts | Refactored to use shared policyCore functions, adds fail-closed vault schema validation |
| packages/ui/src/features/Firegate/index.tsx | Integrates MembraneGate component and imports DEFAULT_GATES from policyCore |
| packages/ui/src/features/Firegate/components/MembraneGate.tsx | New entry gate UI with hold interaction, frantic-detection, and keyboard accessibility |
| shared/src/i18n/en.json | Adds English translations for membrane gate UI |
| shared/src/i18n/es.json | Adds Spanish translations for membrane gate UI |
| shared/src/i18n/ro.json | Adds Romanian translations for membrane gate UI |
| scripts/fgpack-export.mjs | New script to export vault archives to .fgpack format with SHA256 checksums |
| scripts/fgpack-verify.mjs | New script to verify .fgpack archive integrity via hash verification |
| docs/fgpack.md | Documentation for .fgpack archive format and security model |
| docker-compose.yml | Docker Compose configuration for self-hosted server and UI services |
| Makefile | Build targets for Docker operations, backup, verify, and policy tests |
| .env.selfhost.example | Example environment configuration for self-hosting |
| README.md | Adds self-host quickstart guide and references to new documentation |
| .gitignore | Adds exports/ directory to ignore list |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| SHOUT_STATIC_LEVEL, | ||
| } from './lib/somatic'; | ||
| import { StaticGrainOverlay } from './components/StaticGrainOverlay'; | ||
| import { SignalQuality, type SignalStatus } from './components/SignalQuality'; |
There was a problem hiding this comment.
SignalStatus is now exported from the shared policyCore module (line 1 of shared/src/policyCore.ts), but this file imports it from the local SignalQuality component. For consistency and maintainability, consider importing SignalStatus from '@shared/policyCore' to use the canonical type definition, especially since DEFAULT_GATES is already being imported from that module on line 39.
| const actual = await sha256File(fullPath); | ||
| if (actual !== expected) { | ||
| errors.push(`hash mismatch for ${relPath}: expected ${expected}, got ${actual}`); |
There was a problem hiding this comment.
If a file listed in the manifest doesn't exist at the expected path, the await sha256File call will throw an error that gets caught by the outer catch handler (line 70-72). This provides a generic error message instead of a specific one indicating which file is missing. Consider wrapping the sha256File call in a try-catch to collect a more descriptive error message about the missing file, similar to how invalid file entries are handled above.
| const actual = await sha256File(fullPath); | |
| if (actual !== expected) { | |
| errors.push(`hash mismatch for ${relPath}: expected ${expected}, got ${actual}`); | |
| try { | |
| const actual = await sha256File(fullPath); | |
| if (actual !== expected) { | |
| errors.push(`hash mismatch for ${relPath}: expected ${expected}, got ${actual}`); | |
| } | |
| } catch (error) { | |
| if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') { | |
| errors.push(`missing file for ${relPath}: expected at ${fullPath}`); | |
| } else { | |
| errors.push( | |
| `error reading file for ${relPath}: ${error instanceof Error ? error.message : String(error)}` | |
| ); | |
| } |
| const vaultMap = JSON.parse(await readFile(vaultMapPath, 'utf8')); | ||
| const filesSet = new Set(['vault_map.json']); | ||
|
|
||
| const levels = vaultMap.focus_levels || {}; | ||
| for (const level of Object.values(levels)) { | ||
| const entries = Array.isArray(level.files) ? level.files : []; | ||
| for (const entry of entries) { | ||
| if (!entry || typeof entry !== 'object') continue; | ||
| if (typeof entry.entry_ref === 'string') filesSet.add(entry.entry_ref); | ||
| if (typeof entry.path === 'string' && entry.path.endsWith('.json')) filesSet.add(entry.path); | ||
| } | ||
| } |
There was a problem hiding this comment.
The export script doesn't validate the vault_map structure before proceeding with the export. If the vault_map is malformed or missing required fields, the export may fail partway through or produce an invalid fgpack. Consider using the validateVaultMap function from policyCore (which is now available in the shared module) to fail fast with clear error messages if the vault structure is invalid, as recommended by the fail-closed approach described in the PR description.
| await cp(absPath, destPath); | ||
| fileMetas.push(await fileMeta(destPath, relPath)); |
There was a problem hiding this comment.
If a file referenced in the vault_map doesn't exist in the vault directory, the cp command at line 61 will fail and the error will be caught by the outer catch handler. This provides a generic error message rather than clearly indicating which file is missing. Consider wrapping this in a try-catch to provide more specific error messages about which files couldn't be found in the vault, helping users diagnose incomplete vault exports.
| await cp(absPath, destPath); | |
| fileMetas.push(await fileMeta(destPath, relPath)); | |
| try { | |
| await cp(absPath, destPath); | |
| fileMetas.push(await fileMeta(destPath, relPath)); | |
| } catch (error) { | |
| if (error && typeof error === 'object' && error.code === 'ENOENT') { | |
| throw new Error( | |
| `Missing file in vault export: '${relPath}' (expected at '${absPath}')` | |
| ); | |
| } | |
| throw error; | |
| } |
What this adds
policyCoremodule (portable, deterministic)./api/vault/resolve.docker-compose.yml,.env.selfhost.example,Makefile..fgpackdocs + export/verify scripts for lineage portability.Follow-up fixes in this PR
Cannot find module .../shared/src/policyCoreby using ESM-safe specifier + package type metadata.yarn@1.22.22for Corepack consistency.Why
This moves Firegate closer to protocol-grade resilience: policy is decoupled from framework code, self-host path is one-command friendly, and memory continuity has a verifiable archive format.
Validation
npx tsc --noEmitnpx vitest run shared/src/__tests__/policyCore.test.tsVIP_VAULT_MAP_PATH=/Users/raz/Desktop/VIP/vault_map.json yarn devnode scripts/fgpack-export.mjs --vault-root /Users/raz/Desktop/VIP --out /tmp/firegate-test.fgpacknode scripts/fgpack-verify.mjs --pack /tmp/firegate-test.fgpackAssembly Notes
VIP_VAULT_DIR).