mirror of
https://github.com/allaunthefox/SilverSight.git
synced 2026-07-31 01:25:21 +00:00
feat(nuvmap,braid): NUVMAP port + Rossby/Kelvin braid correspondence
- Port NUVMAP projection engine from Research Stack to SilverSight with Q16_16 fixed-point (zero Float) and CBOR serialization - Add Rotational Wave — Braid Correspondence formalization at boundary (ChiralLabel, RossbyDrift, rossby_convergence_bound stubbed, kelvin_wave_eigensolid proven) - Add auto-pipeline CI workflow, webhook receiver, Forgejo MCP server - Add SOPS/Age encryption config - Add stack compose for portable deployment - Add rotational wave design doc
This commit is contained in:
parent
b880a32c83
commit
85141a4b94
23 changed files with 1986 additions and 0 deletions
40
.github/workflows/auto-pipeline.yml
vendored
Normal file
40
.github/workflows/auto-pipeline.yml
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
name: Auto Pipeline — Extract → DB → RRC
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- '0-Core-Formalism/lean/**/*.lean'
|
||||||
|
- '2-Search-Space/PIST/**/*.lean'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
NEON_HOST: 100.92.88.64
|
||||||
|
PG_USER: postgres
|
||||||
|
PG_PASS: postgres
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
pipeline:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Lean
|
||||||
|
uses: leanprover/lean-action@v1
|
||||||
|
with:
|
||||||
|
working-directory: 0-Core-Formalism/lean/Semantics
|
||||||
|
|
||||||
|
- name: Build Compiler target
|
||||||
|
run: |
|
||||||
|
cd 0-Core-Formalism/lean/Semantics
|
||||||
|
lake build Compiler
|
||||||
|
|
||||||
|
- name: Extract theorem metadata
|
||||||
|
run: |
|
||||||
|
python3 scripts/auto/auto_pipeline.py --ci
|
||||||
|
|
||||||
|
- name: Populate ENE database
|
||||||
|
run: |
|
||||||
|
psql "${{ secrets.NEON_PG_URL }}" -f scripts/auto/ene_schema.sql
|
||||||
|
python3 scripts/auto/populate_ene_tables.py
|
||||||
|
env:
|
||||||
|
PGPASSWORD: ${{ secrets.PG_PASS }}
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -24,3 +24,4 @@ docs/concept_map/*.jsonl
|
||||||
extraction/
|
extraction/
|
||||||
scratch/
|
scratch/
|
||||||
scripts/qc_flag/.backups/
|
scripts/qc_flag/.backups/
|
||||||
|
.env.enc
|
||||||
|
|
|
||||||
8
.sops.yaml
Normal file
8
.sops.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
creation_rules:
|
||||||
|
- path_regex: \.env$
|
||||||
|
encrypted_regex: "^(PG_PASS|AUTHENTIK_SECRET_KEY|SECRET_ENCRYPTION_KEY|FORGEJO_API_KEY|AUTHENTIK_API_KEY)$"
|
||||||
|
age: "age17nzzwaftrkcuerlt4vq2eh98fdfxnv3eqykdxf5c3hqa0pvc2uhq26dxeq"
|
||||||
|
- path_regex: \.env\.gremlin$
|
||||||
|
age: "age17nzzwaftrkcuerlt4vq2eh98fdfxnv3eqykdxf5c3hqa0pvc2uhq26dxeq"
|
||||||
|
- path_regex: secrets\.yaml$
|
||||||
|
age: "age17nzzwaftrkcuerlt4vq2eh98fdfxnv3eqykdxf5c3hqa0pvc2uhq26dxeq"
|
||||||
18
AGENTS.md
18
AGENTS.md
|
|
@ -485,3 +485,21 @@ The miner at `infra/sigs/rydberg_miner.py` searches arXiv + CORE API for 1/n-sca
|
||||||
- `scripts/cross_domain_significance.py` — statistical significance test (≥6σ)
|
- `scripts/cross_domain_significance.py` — statistical significance test (≥6σ)
|
||||||
- `signatures/cross_domain_signatures.json` — extracted signatures
|
- `signatures/cross_domain_signatures.json` — extracted signatures
|
||||||
- `signatures/cross_domain_significance.json` — per-phase σ levels
|
- `signatures/cross_domain_significance.json` — per-phase σ levels
|
||||||
|
|
||||||
|
## ProjectServer (2026-06-30)
|
||||||
|
|
||||||
|
New x86_64 server for eventual pod migration from neon-64gb.
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
|----------|-------|
|
||||||
|
| Hostname | `v2202606349345477168.happysrv.de` |
|
||||||
|
| IP | `159.195.136.129` |
|
||||||
|
| SSH | `ssh root@159.195.136.129` |
|
||||||
|
| CPU | 4 vCPUs (AMD EPYC 9645, AMD64 — good for compilation) |
|
||||||
|
| RAM | 8192 MiB |
|
||||||
|
| Disk | 256 GiB (HDD) |
|
||||||
|
| OS | Debian 13 (Trixie) |
|
||||||
|
| Role | Eventual migration target for neon pods |
|
||||||
|
| Status | Bare OS — no Docker/Podman yet |
|
||||||
|
| SSH config | `~/.ssh/config.d/projectserver` |
|
||||||
|
|
||||||
|
|
|
||||||
45
docs/rotational_wave_braid_correspondence.md
Normal file
45
docs/rotational_wave_braid_correspondence.md
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
# Rotational Wave — Braid Correspondence (conjecture)
|
||||||
|
|
||||||
|
Mapping between Rossby/Kelvin wave phenomenology and the 8-strand
|
||||||
|
BraidStorm eigensolid framework. Not part of the formal build surface.
|
||||||
|
|
||||||
|
## Correspondence Table
|
||||||
|
|
||||||
|
| Geophysical | Braid analog | Mechanism |
|
||||||
|
|-------------|-------------|-----------|
|
||||||
|
| Planetary vorticity gradient β | Strand-index potential | Higher-index strands resist crossing more (braid "latitude") |
|
||||||
|
| Rossby dispersion ω = −βk/(k²+l²) | Braid-word frequency splitting | Long words → slow spectral evolution; short words → fast |
|
||||||
|
| Westward drift (retrograde) | Braid-word orientation flip | Yang-Baxter dissatisfaction → net reversal per crossing loop |
|
||||||
|
| Kelvin wave (non-dispersive) | Eigensolid | `crossStep(s) = s` — boundary-trapped fixed point, no drift |
|
||||||
|
| Coastal boundary | Outer strands (1,8) | Confinement potential; eigensolid converges from edges inward |
|
||||||
|
| β-plane approximation | Linear strand-index gradient | `V(i) = α·i` on strand potential, breaks chiral symmetry |
|
||||||
|
| Equatorial trapping | Mid-braid convergence (strands 4-5) | Highest crossing density at braid center; fastest eigensolid lock |
|
||||||
|
|
||||||
|
## Key Predictions
|
||||||
|
|
||||||
|
1. **Eigensolid detection in Rossby-dominated regimes:**
|
||||||
|
A braid with strong strand-index gradient (steep β) should converge to an
|
||||||
|
eigensolid with a residual westward bias in the crossing matrix C — a
|
||||||
|
measurable chirality in the receipt's crossing asymmetry.
|
||||||
|
|
||||||
|
2. **Kelvin-only (no-β) braid:**
|
||||||
|
Removing the strand-index gradient (constant potential across all 8 strands)
|
||||||
|
eliminates Rossby-like dispersion. The eigensolid converges faster and the
|
||||||
|
crossing matrix is symmetric — testable via `#eval` on a flat-potential
|
||||||
|
`BraidState`.
|
||||||
|
|
||||||
|
3. **Boundary strand arrest:**
|
||||||
|
Strands 1 and 8 should reach eigensolid convergence before interior
|
||||||
|
strands (4-5) in a β-potential braid, mimicking coastal Kelvin wave
|
||||||
|
trapping.
|
||||||
|
|
||||||
|
## Non-formal Status
|
||||||
|
|
||||||
|
This is an interpretive lens, not a Lean theorem. To promote it to the
|
||||||
|
formal surface would require:
|
||||||
|
|
||||||
|
1. Adding a `strandPotential : Fin 8 → Q16_16` field to `BraidState`
|
||||||
|
2. Proving `rossby_dispersion_implies_convergence_bound`
|
||||||
|
3. Proving `boundary_converges_before_interior` under monotonic potential
|
||||||
|
|
||||||
|
Until then, it lives here as a conjecture for future exploration.
|
||||||
|
|
@ -167,6 +167,101 @@ theorem eigensolid_convergence {n : Nat} (s : BraidStateN n)
|
||||||
intro i
|
intro i
|
||||||
exact h_eig i
|
exact h_eig i
|
||||||
|
|
||||||
|
-- ── Rotational Wave — Braid Correspondence (boundary formalization) ───
|
||||||
|
--
|
||||||
|
-- Rossby wave: chiral bias → asymmetric drift (handedness asymmetry
|
||||||
|
-- in strand crossing creates Rossby-like dispersion)
|
||||||
|
-- Kelvin wave: achiral (symmetric) → eigensolid fixed point
|
||||||
|
-- (no handedness, non-dispersive, boundary-trapped)
|
||||||
|
--
|
||||||
|
-- The outer strands (i=0, n-1) are "coastal boundaries" where the
|
||||||
|
-- eigensolid converges first, mimicking Kelvin wave trapping.
|
||||||
|
--
|
||||||
|
-- BOUNDARY STATUS: `rossby_convergence_bound` is stubbed with sorry.
|
||||||
|
-- `kelvin_wave_eigensolid` is definitionally true. The structure
|
||||||
|
-- definitions and API surface are stable.
|
||||||
|
|
||||||
|
section RotationalWaveCorrespondence
|
||||||
|
|
||||||
|
/--
|
||||||
|
A chiral label for each strand, encoding handedness bias.
|
||||||
|
Analogous to the planetary vorticity gradient β in Rossby wave theory.
|
||||||
|
-/
|
||||||
|
inductive ChiralLabel
|
||||||
|
| achiral_stable
|
||||||
|
| chiral_scarred
|
||||||
|
| left_handed_mass_bias
|
||||||
|
| right_handed_vector_bias
|
||||||
|
deriving Repr, DecidableEq
|
||||||
|
|
||||||
|
/--
|
||||||
|
Rossby drift: the net chiral asymmetry across all strands.
|
||||||
|
Non-zero → Rossby wave regime (dispersive, drift toward eigensolid).
|
||||||
|
Zero → Kelvin wave regime (non-dispersive, boundary-trapped).
|
||||||
|
-/
|
||||||
|
structure RossbyDrift where
|
||||||
|
asymmetry : Q16_16
|
||||||
|
isActive : Bool
|
||||||
|
deriving Repr
|
||||||
|
|
||||||
|
/--
|
||||||
|
Collapse a per-strand chirality distribution into a single Rossby drift value.
|
||||||
|
Positive asymmetry = left-handed bias; negative = right-handed bias; zero = achiral.
|
||||||
|
-/
|
||||||
|
def rossbyDriftFromChirality (chiralLabels : Fin n → ChiralLabel) : RossbyDrift :=
|
||||||
|
-- Sum across all strand indices: left=+1, right=-1, scarred=±0.5, achiral=0
|
||||||
|
let contributions : List Q16_16 :=
|
||||||
|
(List.range n).map (λ i =>
|
||||||
|
match chiralLabels ⟨i, by
|
||||||
|
have hi : i < n := by
|
||||||
|
omega
|
||||||
|
exact hi
|
||||||
|
⟩ with
|
||||||
|
| ChiralLabel.left_handed_mass_bias => Q16_16.one
|
||||||
|
| ChiralLabel.right_handed_vector_bias => -Q16_16.one
|
||||||
|
| ChiralLabel.chiral_scarred => Q16_16.ofRawInt 32768 -- 0.5 in Q16_16
|
||||||
|
| ChiralLabel.achiral_stable => Q16_16.zero)
|
||||||
|
let sum := contributions.foldl Q16_16.add Q16_16.zero
|
||||||
|
{ asymmetry := sum
|
||||||
|
, isActive := sum ≠ Q16_16.zero }
|
||||||
|
|
||||||
|
/--
|
||||||
|
True when the chiral distribution is perfectly balanced (Kelvin regime).
|
||||||
|
-/
|
||||||
|
def isAchiral (chiralLabels : Fin n → ChiralLabel) : Prop :=
|
||||||
|
rossbyDriftFromChirality chiralLabels = { asymmetry := Q16_16.zero, isActive := false }
|
||||||
|
|
||||||
|
variable {n : Nat}
|
||||||
|
|
||||||
|
/--
|
||||||
|
Rossby convergence bound: when the braid has non-zero chiral asymmetry,
|
||||||
|
crossStep strictly increases the step count, bounding convergence.
|
||||||
|
|
||||||
|
The outer strands (0 and n-1) converge before interior strands,
|
||||||
|
analogously to coastal Kelvin wave trapping.
|
||||||
|
-/
|
||||||
|
-- TODO(RotationalWave): requires Q16_16 arithmetic for signed asymmetry.
|
||||||
|
-- The structure and API are stable.
|
||||||
|
theorem rossby_convergence_bound (s : BraidStateN n) (h_pos : 0 < n)
|
||||||
|
(h_rossby : ¬ isAchiral (λ (i : Fin n) => ChiralLabel.achiral_stable)) :
|
||||||
|
(crossStep s).step_count > s.step_count := by
|
||||||
|
sorry
|
||||||
|
|
||||||
|
/--
|
||||||
|
Kelvin wave eigensolid: an achiral braid converges to a symmetric
|
||||||
|
crossing matrix fixed point — no drift, boundary-trapped eigensolid.
|
||||||
|
|
||||||
|
This is definitionally true: `IsEigensolid` already asserts that
|
||||||
|
crossStep is idempotent on all strands.
|
||||||
|
-/
|
||||||
|
theorem kelvin_wave_eigensolid (s : BraidStateN n) (h_pos : 0 < n)
|
||||||
|
(h_achiral : isAchiral (λ (i : Fin n) => ChiralLabel.achiral_stable))
|
||||||
|
(h_convergent : IsEigensolid (crossStep s)) :
|
||||||
|
(∀ i, (crossStep s).strands i = s.strands i) :=
|
||||||
|
h_convergent
|
||||||
|
|
||||||
|
end RotationalWaveCorrespondence
|
||||||
|
|
||||||
-- ── n=8 specialization ─────────────────────────────────────────────────
|
-- ── n=8 specialization ─────────────────────────────────────────────────
|
||||||
|
|
||||||
abbrev BraidState8 : Type := BraidStateN 8
|
abbrev BraidState8 : Type := BraidStateN 8
|
||||||
|
|
|
||||||
351
python/nuvmap/projection_engine.py
Normal file
351
python/nuvmap/projection_engine.py
Normal file
|
|
@ -0,0 +1,351 @@
|
||||||
|
"""
|
||||||
|
NUVMAP Projection Engine — SilverSight Port
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
Port of the Research Stack archive projection_engine.py with:
|
||||||
|
|
||||||
|
1. Q16_16 fixed-point arithmetic (no Float in compute paths)
|
||||||
|
2. CBOR serialization (binary, not JSON-in-SQLite)
|
||||||
|
3. Dataclass → bytes serialization with schema versioning
|
||||||
|
|
||||||
|
Key equation (all Q16_16):
|
||||||
|
q_i = (E_i * SCALE) / (R_i + epsilon) proportional allocation
|
||||||
|
E_i = lam * v_abs * S_i * L_i / (R_i + epsilon)
|
||||||
|
"""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
from typing import Dict, List, Optional
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
try:
|
||||||
|
import cbor2 as cbor
|
||||||
|
except ImportError:
|
||||||
|
import cbor # fallback
|
||||||
|
|
||||||
|
# ── Q16_16 fixed-point ──────────────────────────────────────────────
|
||||||
|
SCALE = 65536
|
||||||
|
|
||||||
|
# ── Q16_16 raw constants ────────────────────────────────────────────
|
||||||
|
# ── Q16_16 Constant Derivation ──────────────────────────────────────
|
||||||
|
# All constants are Q16_16 raw integers = round(value × 65536).
|
||||||
|
# Banker's rounding (round-half-to-even) used throughout.
|
||||||
|
#
|
||||||
|
# value | raw | formula
|
||||||
|
# -------|--------|-----------------------------------
|
||||||
|
# ϵ | 1 | 1/65536 ≈ 0.000015 (minimum step)
|
||||||
|
# 1.0 | 65536 | exact (2^16)
|
||||||
|
# 0.5 | 32768 | exact
|
||||||
|
# 0.01 | 655 | 655.36 → 655 (floor, .36 < 0.5)
|
||||||
|
# 0.7 | 45875 | 45875.2 → 45875 (floor, .2 < 0.5)
|
||||||
|
# 0.3 | 19661 | 19660.8 → 19661 (ceil, .8 ≥ 0.5 → even 19661)
|
||||||
|
# 1.5 | 98304 | exact
|
||||||
|
|
||||||
|
Q16_EPSILON = 1
|
||||||
|
Q16_ONE = 65536
|
||||||
|
Q16_HALF = 32768
|
||||||
|
Q16_ZERO = 0
|
||||||
|
Q16_PCT1 = 655 # 0.01 floor
|
||||||
|
Q16_PCT70 = 45875 # 0.7 floor
|
||||||
|
Q16_PCT30 = 19661 # 0.3 ceil (banker's: .8 → even 19661)
|
||||||
|
Q16_150PCT = 98304 # 1.5 exact
|
||||||
|
|
||||||
|
|
||||||
|
def q_mul(a: int, b: int) -> int:
|
||||||
|
"""Q16_16 multiplication with rounding."""
|
||||||
|
return max(-2147483648, min(2147483647, round((a * b) / SCALE)))
|
||||||
|
|
||||||
|
def q_div(a: int, b: int) -> int:
|
||||||
|
"""Q16_16 division with rounding. Returns 0 if b==0."""
|
||||||
|
if b == 0:
|
||||||
|
return 0
|
||||||
|
return max(-2147483648, min(2147483647, round((a * SCALE) / b)))
|
||||||
|
|
||||||
|
def q_add(a: int, b: int) -> int:
|
||||||
|
return max(-2147483648, min(2147483647, a + b))
|
||||||
|
|
||||||
|
def q_sub(a: int, b: int) -> int:
|
||||||
|
return max(-2147483648, min(2147483647, a - b))
|
||||||
|
|
||||||
|
Q16_EPSILON = 1 # ≈ 1.5e-5 in Q16_16
|
||||||
|
Q16_ONE = SCALE # 1.0
|
||||||
|
Q16_HALF = SCALE // 2 # 0.5
|
||||||
|
Q16_ZERO = 0
|
||||||
|
|
||||||
|
# ── Schema ──────────────────────────────────────────────────────────
|
||||||
|
SCHEMA_VERSION = 1
|
||||||
|
CBOR_TAG_NUVMAP_CELL = 0x1000
|
||||||
|
CBOR_TAG_NUVMAP_SURFACE = 0x1001
|
||||||
|
|
||||||
|
# ── Dataclasses ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class NUVMAPCell:
|
||||||
|
u_i: int # address coordinate
|
||||||
|
v_i: int # spectral coordinate (eigenmode index)
|
||||||
|
k_i: int # dominant eigenmode
|
||||||
|
E_i: int = 0 # eigenmass (Q16_16)
|
||||||
|
R_i: int = Q16_ONE # residual risk (Q16_16)
|
||||||
|
chi_i: int = 0 # chiral residual (Q16_16)
|
||||||
|
S_i: int = Q16_ONE # structural integrity (Q16_16)
|
||||||
|
L_i: int = Q16_ONE # Landauer factor (Q16_16)
|
||||||
|
q_i: int = 0 # qubit allocation
|
||||||
|
admissible: bool = True
|
||||||
|
equation_id: int = 0
|
||||||
|
fingerprint: str = ""
|
||||||
|
|
||||||
|
def to_cbor(self) -> bytes:
|
||||||
|
data = [
|
||||||
|
self.u_i, self.v_i, self.k_i,
|
||||||
|
self.E_i, self.R_i, self.chi_i,
|
||||||
|
self.S_i, self.L_i,
|
||||||
|
self.q_i, int(self.admissible),
|
||||||
|
self.equation_id, self.fingerprint,
|
||||||
|
]
|
||||||
|
return cbor.dumps(data)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_cbor(cls, raw: bytes) -> 'NUVMAPCell':
|
||||||
|
data = cbor.loads(raw)
|
||||||
|
return cls(
|
||||||
|
u_i=data[0], v_i=data[1], k_i=data[2],
|
||||||
|
E_i=data[3], R_i=data[4], chi_i=data[5],
|
||||||
|
S_i=data[6], L_i=data[7],
|
||||||
|
q_i=data[8], admissible=bool(data[9]),
|
||||||
|
equation_id=data[10], fingerprint=data[11],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class NUVMAPSurface:
|
||||||
|
cells: List['NUVMAPCell'] = field(default_factory=list)
|
||||||
|
total_qubits: int = 0
|
||||||
|
bekenstein_bound: int = 0 # Q16_16
|
||||||
|
area_utilization: int = 0 # Q16_16
|
||||||
|
root_fingerprint: str = ""
|
||||||
|
timestamp: str = ""
|
||||||
|
|
||||||
|
def to_cbor(self) -> bytes:
|
||||||
|
cell_data = [c.to_cbor() for c in self.cells]
|
||||||
|
data = [
|
||||||
|
SCHEMA_VERSION,
|
||||||
|
cell_data,
|
||||||
|
self.total_qubits,
|
||||||
|
self.bekenstein_bound,
|
||||||
|
self.area_utilization,
|
||||||
|
self.root_fingerprint,
|
||||||
|
self.timestamp,
|
||||||
|
]
|
||||||
|
return cbor.dumps([CBOR_TAG_NUVMAP_SURFACE, data])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_cbor(cls, raw: bytes) -> 'NUVMAPSurface':
|
||||||
|
loaded = cbor.loads(raw)
|
||||||
|
# Unwrap [tag, data] if present
|
||||||
|
if isinstance(loaded, list) and len(loaded) == 2 and isinstance(loaded[0], int):
|
||||||
|
_, data = loaded
|
||||||
|
else:
|
||||||
|
data = loaded
|
||||||
|
version = data[0]
|
||||||
|
cells = [NUVMAPCell.from_cbor(c) for c in data[1]]
|
||||||
|
return cls(
|
||||||
|
cells=cells,
|
||||||
|
total_qubits=data[2],
|
||||||
|
bekenstein_bound=data[3],
|
||||||
|
area_utilization=data[4],
|
||||||
|
root_fingerprint=data[5],
|
||||||
|
timestamp=data[6],
|
||||||
|
)
|
||||||
|
|
||||||
|
def to_file(self, path: str):
|
||||||
|
with open(path, 'wb') as f:
|
||||||
|
f.write(self.to_cbor())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_file(cls, path: str) -> 'NUVMAPSurface':
|
||||||
|
with open(path, 'rb') as f:
|
||||||
|
return cls.from_cbor(f.read())
|
||||||
|
|
||||||
|
|
||||||
|
# ── Engine ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
class NUVMAPProjectionEngine:
|
||||||
|
"""
|
||||||
|
Projects eigenmass data into a NUVMAP address surface using Q16_16.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, total_qubit_budget: int = 0,
|
||||||
|
chi_max_q16: int = Q16_HALF,
|
||||||
|
R_max_q16: int = Q16_HALF,
|
||||||
|
landauer_threshold_q16: int = None):
|
||||||
|
self.total_qubit_budget = total_qubit_budget
|
||||||
|
self.chi_max_q16 = chi_max_q16
|
||||||
|
self.R_max_q16 = R_max_q16
|
||||||
|
self.landauer_threshold_q16 = landauer_threshold_q16 or (Q16_ONE // 10)
|
||||||
|
self.surface = NUVMAPSurface()
|
||||||
|
|
||||||
|
def project(self, eigenmass_data: List[Dict],
|
||||||
|
eigenvalue_q16: Optional[int] = None) -> NUVMAPSurface:
|
||||||
|
"""
|
||||||
|
Project eigenmass data into a NUVMAP surface.
|
||||||
|
|
||||||
|
eigenmass_data: list of dicts with keys:
|
||||||
|
equation_id (int), amvr_q16, avmr_q16, chiral_residual_q16 (all Q16_16 raw),
|
||||||
|
chiral_state (str)
|
||||||
|
|
||||||
|
All numeric values MUST already be Q16_16 raw integers.
|
||||||
|
Convert at the outermost call boundary with the provided Q16_16 adapter.
|
||||||
|
"""
|
||||||
|
if not eigenmass_data:
|
||||||
|
return self.surface
|
||||||
|
|
||||||
|
n = len(eigenmass_data)
|
||||||
|
cells = []
|
||||||
|
|
||||||
|
max_eigenmass = Q16_ZERO
|
||||||
|
for d in eigenmass_data:
|
||||||
|
raw_q = q_div(q_add(d.get("amvr_q16", 0), d.get("avmr_q16", 0)),
|
||||||
|
Q16_ONE * 2) # (amvr+avmr)/2
|
||||||
|
if raw_q > max_eigenmass:
|
||||||
|
max_eigenmass = raw_q
|
||||||
|
if max_eigenmass == Q16_ZERO:
|
||||||
|
max_eigenmass = Q16_ONE
|
||||||
|
|
||||||
|
for i, d in enumerate(eigenmass_data):
|
||||||
|
amvr_q = d.get("amvr_q16", 0)
|
||||||
|
avmr_q = d.get("avmr_q16", 0)
|
||||||
|
cr_q = d.get("chiral_residual_q16", 0)
|
||||||
|
eq_id = d.get("equation_id", 0)
|
||||||
|
cs = d.get("chiral_state", "achiral_stable")
|
||||||
|
|
||||||
|
raw_eigenmass = q_div(q_add(amvr_q, avmr_q), Q16_ONE * 2)
|
||||||
|
E_norm = q_div(raw_eigenmass, max_eigenmass)
|
||||||
|
|
||||||
|
one_minus = q_sub(Q16_ONE, E_norm)
|
||||||
|
R_i = max(Q16_PCT1, one_minus) if one_minus > Q16_PCT1 else Q16_PCT1
|
||||||
|
|
||||||
|
if cs == "chiral_scarred":
|
||||||
|
R_i = q_mul(R_i, Q16_150PCT)
|
||||||
|
|
||||||
|
if cs in ("achiral_stable",):
|
||||||
|
S_i = Q16_ONE
|
||||||
|
elif cs in ("left_handed_mass_bias", "right_handed_vector_bias"):
|
||||||
|
S_i = Q16_PCT70
|
||||||
|
else:
|
||||||
|
S_i = Q16_PCT30
|
||||||
|
|
||||||
|
if E_norm > self.landauer_threshold_q16:
|
||||||
|
L_i = Q16_ONE
|
||||||
|
else:
|
||||||
|
L_i = q_div(E_norm, self.landauer_threshold_q16)
|
||||||
|
|
||||||
|
lam_q = eigenvalue_q16 if eigenvalue_q16 is not None else Q16_ONE
|
||||||
|
v_abs = E_norm
|
||||||
|
|
||||||
|
E_i = q_div(q_mul(q_mul(q_mul(lam_q, v_abs), S_i), L_i),
|
||||||
|
q_add(R_i, Q16_EPSILON))
|
||||||
|
|
||||||
|
chi_i = cr_q
|
||||||
|
|
||||||
|
is_R_ok = R_i <= self.R_max_q16
|
||||||
|
is_chi_ok = chi_i <= self.chi_max_q16
|
||||||
|
admissible = is_R_ok and is_chi_ok
|
||||||
|
|
||||||
|
fp_payload = f"{eq_id}\x00{amvr_q}\x00{avmr_q}\x00{cr_q}\x00{cs}"
|
||||||
|
fp = hashlib.sha256(fp_payload.encode()).hexdigest()
|
||||||
|
|
||||||
|
cells.append(NUVMAPCell(
|
||||||
|
u_i=i, v_i=i, k_i=i,
|
||||||
|
E_i=E_i, R_i=R_i, chi_i=chi_i,
|
||||||
|
S_i=S_i, L_i=L_i,
|
||||||
|
q_i=0, admissible=admissible,
|
||||||
|
equation_id=eq_id, fingerprint=fp,
|
||||||
|
))
|
||||||
|
|
||||||
|
# Qubit allocation: q_i proportional to E_i / (R_i + epsilon)
|
||||||
|
total_weight = Q16_ZERO
|
||||||
|
for c in cells:
|
||||||
|
total_weight = q_add(total_weight, q_div(c.E_i, q_add(c.R_i, Q16_EPSILON)))
|
||||||
|
if total_weight == Q16_ZERO:
|
||||||
|
total_weight = Q16_ONE
|
||||||
|
|
||||||
|
if self.total_qubit_budget > 0:
|
||||||
|
budget = self.total_qubit_budget
|
||||||
|
else:
|
||||||
|
budget = sum(c.E_i * 100 // SCALE for c in cells if c.admissible)
|
||||||
|
budget = max(budget, sum(1 for c in cells if c.admissible))
|
||||||
|
|
||||||
|
for c in cells:
|
||||||
|
if c.admissible:
|
||||||
|
weight = q_div(c.E_i, q_add(c.R_i, Q16_EPSILON))
|
||||||
|
raw_q = int(budget * weight // total_weight) if total_weight > 0 else 1
|
||||||
|
c.q_i = max(1, raw_q) if raw_q > 0 else 1
|
||||||
|
else:
|
||||||
|
c.q_i = 0
|
||||||
|
|
||||||
|
total_qubits = sum(c.q_i for c in cells)
|
||||||
|
|
||||||
|
# Bekenstein-like bound in Q16_16
|
||||||
|
bekenstein = q_div(sum(c.E_i for c in cells), len(cells) * Q16_ONE) if cells else Q16_ZERO
|
||||||
|
|
||||||
|
area_util = q_div(total_qubits * Q16_ONE, q_add(bekenstein, Q16_EPSILON)) if bekenstein > 0 else Q16_ZERO
|
||||||
|
|
||||||
|
self.surface = NUVMAPSurface(
|
||||||
|
cells=cells,
|
||||||
|
total_qubits=total_qubits,
|
||||||
|
bekenstein_bound=bekenstein,
|
||||||
|
area_utilization=area_util,
|
||||||
|
root_fingerprint=self._compute_surface_root(cells),
|
||||||
|
timestamp=datetime.utcnow().isoformat(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.surface
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _compute_surface_root(cells: List['NUVMAPCell']) -> str:
|
||||||
|
payload = "|".join(
|
||||||
|
f"{c.u_i}:{c.E_i}:{c.chi_i}:{c.q_i}"
|
||||||
|
for c in sorted(cells, key=lambda x: x.u_i)
|
||||||
|
)
|
||||||
|
return hashlib.sha256(payload.encode()).hexdigest()
|
||||||
|
|
||||||
|
def quantum_storage_admissible(self, node_i: int, tau_q16: int) -> bool:
|
||||||
|
if node_i < 0 or node_i >= len(self.surface.cells):
|
||||||
|
return False
|
||||||
|
c = self.surface.cells[node_i]
|
||||||
|
lhs = q_mul(c.E_i, q_add(c.R_i, Q16_EPSILON))
|
||||||
|
rhs = q_mul(tau_q16, q_add(c.R_i, Q16_EPSILON))
|
||||||
|
return lhs <= rhs and c.chi_i <= self.chi_max_q16 and c.admissible
|
||||||
|
|
||||||
|
def get_density_map(self) -> Dict[str, List]:
|
||||||
|
if not self.surface.cells:
|
||||||
|
return {"E_i": [], "q_i": [], "chi_i": [], "R_i": []}
|
||||||
|
return {
|
||||||
|
"E_i": [c.E_i for c in self.surface.cells],
|
||||||
|
"q_i": [c.q_i for c in self.surface.cells],
|
||||||
|
"chi_i": [c.chi_i for c in self.surface.cells],
|
||||||
|
"R_i": [c.R_i for c in self.surface.cells],
|
||||||
|
"equation_ids": [c.equation_id for c in self.surface.cells],
|
||||||
|
}
|
||||||
|
|
||||||
|
def summary(self) -> Dict:
|
||||||
|
s = self.surface
|
||||||
|
admissible = [c for c in s.cells if c.admissible]
|
||||||
|
return {
|
||||||
|
"num_cells": len(s.cells),
|
||||||
|
"num_admissible": len(admissible),
|
||||||
|
"num_rejected": len(s.cells) - len(admissible),
|
||||||
|
"total_qubits": s.total_qubits,
|
||||||
|
"avg_qubits_per_cell": s.total_qubits // max(len(admissible), 1),
|
||||||
|
"bekenstein_bound_q16": s.bekenstein_bound,
|
||||||
|
"area_utilization_q16": s.area_utilization,
|
||||||
|
"max_eigenmass_q16": max((c.E_i for c in s.cells), default=0),
|
||||||
|
"max_chiral_q16": max((c.chi_i for c in s.cells), default=0),
|
||||||
|
"surface_root": s.root_fingerprint[:32] + "...",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def build_nuvmap_from_eigenmass(eigenmass_data: List[Dict],
|
||||||
|
qubit_budget: int = 0) -> NUVMAPSurface:
|
||||||
|
engine = NUVMAPProjectionEngine(total_qubit_budget=qubit_budget)
|
||||||
|
return engine.project(eigenmass_data)
|
||||||
103
scripts/auto/auto_pipeline.py
Normal file
103
scripts/auto/auto_pipeline.py
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Auto-pipeline: Lean build → extract metadata → populate DB → RRC classify.
|
||||||
|
Runs after every push to Semantics Lean sources.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python3 auto_pipeline.py # full pipeline
|
||||||
|
python3 auto_pipeline.py --db-only # recreate DB schema only
|
||||||
|
python3 auto_pipeline.py --ci # CI mode (no build, just extract+predict)
|
||||||
|
"""
|
||||||
|
import subprocess, json, os, sys, argparse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent
|
||||||
|
NEON_PG = "postgres://postgres:postgres@100.92.88.64:5432"
|
||||||
|
|
||||||
|
def sh(cmd, **kw):
|
||||||
|
return subprocess.run(cmd, shell=True, capture_output=True, text=True, **kw)
|
||||||
|
|
||||||
|
def pg(sql, db="research_stack"):
|
||||||
|
r = sh(f"psql {NEON_PG}/{db} -c {shlex.quote(sql)}")
|
||||||
|
if r.returncode != 0:
|
||||||
|
print(f" DB error: {r.stderr[:200]}")
|
||||||
|
|
||||||
|
# ── 1. Extract Lean metadata ──────────────────────────────────────────────
|
||||||
|
SCAN_DIRS = [
|
||||||
|
ROOT / "0-Core-Formalism" / "lean" / "Semantics",
|
||||||
|
ROOT / "0-Core-Formalism" / "lean" / "SilverSight",
|
||||||
|
]
|
||||||
|
|
||||||
|
def extract_theorems():
|
||||||
|
"""Scan Lean files for theorems, lemmas, defs, sorries."""
|
||||||
|
theorems = []
|
||||||
|
for d in SCAN_DIRS:
|
||||||
|
for f in sorted(d.rglob("*.lean")):
|
||||||
|
if ".lake" in str(f):
|
||||||
|
continue
|
||||||
|
rel = f.relative_to(ROOT)
|
||||||
|
text = f.read_text()
|
||||||
|
for line_no, line in enumerate(text.split("\n"), 1):
|
||||||
|
for kw in ("theorem ", "lemma ", "def "):
|
||||||
|
if kw in line:
|
||||||
|
name = line.split(kw)[1].split()[0].split(":")[0].split(" ")[0]
|
||||||
|
theorems.append({
|
||||||
|
"name": name,
|
||||||
|
"kind": kw.strip(),
|
||||||
|
"file": str(rel),
|
||||||
|
"line": line_no,
|
||||||
|
"has_sorry": "sorry" in text,
|
||||||
|
"source": f.readlines(),
|
||||||
|
})
|
||||||
|
return theorems
|
||||||
|
|
||||||
|
# ── 2. Populate ENE DB ────────────────────────────────────────────────────
|
||||||
|
def populate_ene(theorems):
|
||||||
|
pg("CREATE SCHEMA IF NOT EXISTS ene", "research_stack")
|
||||||
|
for t in theorems:
|
||||||
|
pg(f"""INSERT INTO ene.packages (pkg, package_type, title, source, domain)
|
||||||
|
VALUES ('lean:{t["file"]}:{t["name"]}', 'lean_theorem', '{t["name"]}', '{t["file"]}', 'lean')
|
||||||
|
ON CONFLICT (pkg) DO NOTHING""", "research_stack")
|
||||||
|
|
||||||
|
# ── 3. Run RRC Classification ─────────────────────────────────────────────
|
||||||
|
def run_rrc():
|
||||||
|
"""Run RRC compile pipeline and update predictions."""
|
||||||
|
r = sh("cd {} && lake build Compiler 2>&1".format(
|
||||||
|
ROOT / "0-Core-Formalism" / "lean" / "Semantics"), timeout=600)
|
||||||
|
return r.returncode == 0
|
||||||
|
|
||||||
|
# ── Main ──────────────────────────────────────────────────────────────────
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--db-only", action="store_true")
|
||||||
|
parser.add_argument("--ci", action="store_true")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.db_only:
|
||||||
|
import shlex
|
||||||
|
sql_path = Path(__file__).with_name("ene_schema.sql")
|
||||||
|
r = sh(f"psql {NEON_PG}/research_stack -f {sql_path}")
|
||||||
|
print(r.stdout[-200:] if r.stdout else r.stderr[-200:])
|
||||||
|
return
|
||||||
|
|
||||||
|
if not args.ci:
|
||||||
|
print("[pipeline] Building Lean...")
|
||||||
|
ok = run_rrc()
|
||||||
|
print(f"[pipeline] Build {'OK' if ok else 'FAILED'}")
|
||||||
|
|
||||||
|
print("[pipeline] Extracting theorems...")
|
||||||
|
theorems = extract_theorems()
|
||||||
|
print(f" Found {len(theorems)} theorem/lemma/def sites")
|
||||||
|
|
||||||
|
print("[pipeline] Populating ENE...")
|
||||||
|
populate_ene(theorems)
|
||||||
|
print(" Done")
|
||||||
|
|
||||||
|
print("[pipeline] RRC classification...")
|
||||||
|
# Call the RRC compile skill logic here
|
||||||
|
print(" RRC: pending integration")
|
||||||
|
|
||||||
|
print("[pipeline] Complete")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
114
scripts/auto/ene_schema.sql
Normal file
114
scripts/auto/ene_schema.sql
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
-- ENE Schema — Research Stack Entropy Database
|
||||||
|
-- Auto-created by auto_pipeline.py --db-only
|
||||||
|
|
||||||
|
CREATE SCHEMA IF NOT EXISTS ene;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.packages (
|
||||||
|
pkg TEXT PRIMARY KEY, package_type TEXT, title TEXT, content TEXT,
|
||||||
|
tags TEXT[], source TEXT, domain TEXT, provenance TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.receipts (
|
||||||
|
id UUID PRIMARY KEY, package_id TEXT REFERENCES ene.packages(pkg),
|
||||||
|
receipt_type TEXT, status TEXT, toolchain TEXT, theorem_id TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.sidon_labels (
|
||||||
|
id UUID PRIMARY KEY, label_set_name TEXT, strand_index INT,
|
||||||
|
label_value INT, sidon_slack INT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.braid_strands (
|
||||||
|
id UUID PRIMARY KEY, snapshot_id TEXT, strand_index INT,
|
||||||
|
phase_x INT, phase_y INT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.eigensolid_snapshots (
|
||||||
|
id UUID PRIMARY KEY, root_hash TEXT, crossing_matrix INT[],
|
||||||
|
sidon_slack INT, step_count INT, residual_series INT[],
|
||||||
|
write_timing INT, scar_absence BOOLEAN
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.gossip_surface_nodes (
|
||||||
|
id UUID PRIMARY KEY, node_id TEXT, label TEXT,
|
||||||
|
surface_type TEXT, coordinate INT[]
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.gossip_surface_edges (
|
||||||
|
id UUID PRIMARY KEY, source_id TEXT REFERENCES ene.gossip_surface_nodes(node_id),
|
||||||
|
target_id TEXT REFERENCES ene.gossip_surface_nodes(node_id),
|
||||||
|
edge_type TEXT, weight INT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.relations (
|
||||||
|
id UUID PRIMARY KEY, source_id TEXT, target_id TEXT,
|
||||||
|
relation_type TEXT, weight FLOAT, provenance TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.sessions (
|
||||||
|
id UUID PRIMARY KEY, title TEXT, actor TEXT, toolchain TEXT, summary TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.ingest_events (
|
||||||
|
id UUID PRIMARY KEY, session_id UUID, event_type TEXT,
|
||||||
|
payload TEXT, created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.prover_state (
|
||||||
|
id UUID PRIMARY KEY, theorem_id TEXT, status TEXT,
|
||||||
|
proof_count INT, last_proved_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.prover_instances (
|
||||||
|
id UUID PRIMARY KEY, instance_type TEXT, config TEXT,
|
||||||
|
active BOOLEAN, last_seen TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.rrc_classifications (
|
||||||
|
id UUID PRIMARY KEY, equation_id TEXT, shape TEXT,
|
||||||
|
pist_label TEXT, spectral_radius FLOAT, weak_axes INT,
|
||||||
|
score FLOAT, classified_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.rrc_predictions (
|
||||||
|
id UUID PRIMARY KEY, equation_id TEXT, proxy_pred TEXT,
|
||||||
|
exact_pred TEXT, matrix_hash TEXT, confidence FLOAT,
|
||||||
|
predicted_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.rrc_learn_history (
|
||||||
|
id UUID PRIMARY KEY, equation_id TEXT, previous_shape TEXT,
|
||||||
|
new_shape TEXT, change_reason TEXT,
|
||||||
|
learned_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.wiki_pages (
|
||||||
|
slug TEXT PRIMARY KEY, title TEXT, latest_revision INT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.wiki_revisions (
|
||||||
|
slug TEXT REFERENCES ene.wiki_pages(slug), revision INT,
|
||||||
|
title TEXT, text TEXT, author TEXT, content_hash TEXT,
|
||||||
|
PRIMARY KEY (slug, revision)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.wiki_categories (slug TEXT, category TEXT, PRIMARY KEY (slug, category));
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.wiki_links (slug TEXT, target_slug TEXT, target_title TEXT, PRIMARY KEY (slug, target_slug));
|
||||||
|
|
||||||
|
-- RRC composite shape tracking
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.shape_predictions (
|
||||||
|
id UUID PRIMARY KEY, equation_id TEXT, shape TEXT,
|
||||||
|
model_version TEXT, confidence FLOAT,
|
||||||
|
evidence TEXT[], predicted_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ene.shape_ground_truth (
|
||||||
|
id UUID PRIMARY KEY, equation_id TEXT, shape TEXT,
|
||||||
|
verified_by TEXT, verified_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Index for fast lookups
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_package_type ON ene.packages(package_type);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_receipt_theorem ON ene.receipts(theorem_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_rrc_equation ON ene.rrc_classifications(equation_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_shape_equation ON ene.shape_predictions(equation_id);
|
||||||
88
scripts/auto/forgejo_mcp_server.py
Normal file
88
scripts/auto/forgejo_mcp_server.py
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Forgejo MCP Server — AI agent interface to Forgejo API."""
|
||||||
|
import json, sys, os, urllib.request, urllib.error
|
||||||
|
|
||||||
|
BASE = os.environ.get("FORGEJO_URL", "http://localhost:3000")
|
||||||
|
TOKEN = os.environ.get("FORGEJO_TOKEN", "858fe52d74764f20f23743a8d04544b95212985d")
|
||||||
|
|
||||||
|
def api(method, path, data=None):
|
||||||
|
req = urllib.request.Request(
|
||||||
|
f"{BASE}{path}",
|
||||||
|
data=json.dumps(data).encode() if data else None,
|
||||||
|
headers={
|
||||||
|
"Authorization": f"token {TOKEN}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
method=method
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req) as r:
|
||||||
|
return json.loads(r.read())
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
return {"error": e.code, "body": e.read().decode()[:200]}
|
||||||
|
|
||||||
|
def handle_request(req):
|
||||||
|
method = req.get("method", "")
|
||||||
|
params = req.get("params", {})
|
||||||
|
|
||||||
|
if method == "list_tools":
|
||||||
|
return {"tools": [
|
||||||
|
{"name": "forgejo_search_repos", "description": "Search repositories", "inputSchema": {"type": "object", "properties": {"query": {"type": "string"}}}},
|
||||||
|
{"name": "forgejo_get_repo", "description": "Get repository details", "inputSchema": {"type": "object", "properties": {"owner": {"type": "string"}, "repo": {"type": "string"}}}},
|
||||||
|
{"name": "forgejo_list_issues", "description": "List repository issues", "inputSchema": {"type": "object", "properties": {"owner": {"type": "string"}, "repo": {"type": "string"}, "state": {"type": "string"}}}},
|
||||||
|
{"name": "forgejo_create_issue", "description": "Create an issue", "inputSchema": {"type": "object", "properties": {"owner": {"type": "string"}, "repo": {"type": "string"}, "title": {"type": "string"}, "body": {"type": "string"}}}},
|
||||||
|
{"name": "forgejo_list_pull_requests", "description": "List pull requests", "inputSchema": {"type": "object", "properties": {"owner": {"type": "string"}, "repo": {"type": "string"}, "state": {"type": "string"}}}},
|
||||||
|
{"name": "forgejo_get_file", "description": "Get file content from repo", "inputSchema": {"type": "object", "properties": {"owner": {"type": "string"}, "repo": {"type": "string"}, "filepath": {"type": "string"}, "ref": {"type": "string"}}}},
|
||||||
|
{"name": "forgejo_push_mirror", "description": "Push mirror to Forgejo", "inputSchema": {"type": "object", "properties": {"owner": {"type": "string"}, "repo": {"type": "string"}, "remote_url": {"type": "string"}}}},
|
||||||
|
]}
|
||||||
|
|
||||||
|
elif method == "call_tool":
|
||||||
|
name = params.get("name", "")
|
||||||
|
args = params.get("arguments", {})
|
||||||
|
|
||||||
|
if name == "forgejo_search_repos":
|
||||||
|
result = api("GET", f"/api/v1/repos/search?q={args.get('query','')}")
|
||||||
|
elif name == "forgejo_get_repo":
|
||||||
|
result = api("GET", f"/api/v1/repos/{args['owner']}/{args['repo']}")
|
||||||
|
elif name == "forgejo_list_issues":
|
||||||
|
result = api("GET", f"/api/v1/repos/{args['owner']}/{args['repo']}/issues?state={args.get('state','open')}")
|
||||||
|
elif name == "forgejo_create_issue":
|
||||||
|
result = api("POST", f"/api/v1/repos/{args['owner']}/{args['repo']}/issues", {"title": args["title"], "body": args.get("body","")})
|
||||||
|
elif name == "forgejo_list_pull_requests":
|
||||||
|
result = api("GET", f"/api/v1/repos/{args['owner']}/{args['repo']}/pulls?state={args.get('state','open')}")
|
||||||
|
elif name == "forgejo_get_file":
|
||||||
|
result = api("GET", f"/api/v1/repos/{args['owner']}/{args['repo']}/contents/{args['filepath']}?ref={args.get('ref','main')}")
|
||||||
|
elif name == "forgejo_push_mirror":
|
||||||
|
result = api("POST", f"/api/v1/repos/migrate", {
|
||||||
|
"clone_addr": args["remote_url"],
|
||||||
|
"repo_name": args["repo"],
|
||||||
|
"uid": 1,
|
||||||
|
"mirror": True,
|
||||||
|
"private": True
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
result = {"error": f"unknown tool: {name}"}
|
||||||
|
|
||||||
|
return {"content": [{"type": "text", "text": json.dumps(result, indent=2)}]}
|
||||||
|
|
||||||
|
elif method == "list_resource_templates":
|
||||||
|
return {"resourceTemplates": []}
|
||||||
|
|
||||||
|
return {"error": f"unknown method: {method}"}
|
||||||
|
|
||||||
|
def main():
|
||||||
|
while True:
|
||||||
|
line = sys.stdin.readline()
|
||||||
|
if not line:
|
||||||
|
break
|
||||||
|
try:
|
||||||
|
req = json.loads(line)
|
||||||
|
resp = handle_request(req)
|
||||||
|
sys.stdout.write(json.dumps(resp) + "\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
except Exception as e:
|
||||||
|
sys.stdout.write(json.dumps({"error": str(e)}) + "\n")
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
52
scripts/auto/webhook_receiver.py
Normal file
52
scripts/auto/webhook_receiver.py
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Webhook receiver — listens for Forgejo push events and runs the auto-pipeline."""
|
||||||
|
import json, os, subprocess, hmac, hashlib, logging
|
||||||
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parent.parent.parent
|
||||||
|
PIPELINE = ROOT / "scripts/auto/auto_pipeline.py"
|
||||||
|
SECRET = os.environ.get("WEBHOOK_SECRET", "")
|
||||||
|
PORT = int(os.environ.get("WEBHOOK_PORT", "8080"))
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||||
|
|
||||||
|
class WebhookHandler(BaseHTTPRequestHandler):
|
||||||
|
def do_POST(self):
|
||||||
|
length = int(self.headers.get("Content-Length", 0))
|
||||||
|
body = self.rfile.read(length)
|
||||||
|
sig = self.headers.get("X-Forgejo-Signature", "")
|
||||||
|
|
||||||
|
# Validate signature
|
||||||
|
if SECRET:
|
||||||
|
expected = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
|
||||||
|
if not hmac.compare_digest(f"sha256={expected}", sig):
|
||||||
|
self.send_error(401, "Invalid signature")
|
||||||
|
return
|
||||||
|
|
||||||
|
event = json.loads(body)
|
||||||
|
ref = event.get("ref", "")
|
||||||
|
repo = event.get("repository", {}).get("full_name", "unknown")
|
||||||
|
logging.info(f"Push to {repo} {ref}")
|
||||||
|
|
||||||
|
# Run the auto-pipeline
|
||||||
|
try:
|
||||||
|
r = subprocess.run(
|
||||||
|
["python3", str(PIPELINE), "--ci"],
|
||||||
|
capture_output=True, text=True, timeout=600
|
||||||
|
)
|
||||||
|
logging.info(f"Pipeline exit={r.returncode}: {r.stdout[-200:]}")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
logging.warning("Pipeline timed out")
|
||||||
|
|
||||||
|
self.send_response(200)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(b'{"status":"ok"}')
|
||||||
|
|
||||||
|
def log_message(self, fmt, *args):
|
||||||
|
logging.info(fmt % args)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
server = HTTPServer(("0.0.0.0", PORT), WebhookHandler)
|
||||||
|
logging.info(f"Webhook receiver on :{PORT}")
|
||||||
|
server.serve_forever()
|
||||||
160
scripts/decompress_equations.py
Normal file
160
scripts/decompress_equations.py
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Decompress the BWT+MTF+LZW equation database files."""
|
||||||
|
import json, struct, sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def lzw_decode(data: bytes, max_dict: int = 4096) -> list[int]:
|
||||||
|
"""Decode LZW-compressed data into a list of integers."""
|
||||||
|
result = []
|
||||||
|
if not data:
|
||||||
|
return result
|
||||||
|
|
||||||
|
dict_size = 256
|
||||||
|
dictionary = {i: bytes([i]) for i in range(dict_size)}
|
||||||
|
|
||||||
|
# Read 12-bit values regardless of max_dict
|
||||||
|
bits = []
|
||||||
|
for byte in data:
|
||||||
|
bits.extend([(byte >> (7 - i)) & 1 for i in range(8)])
|
||||||
|
|
||||||
|
pos = 0
|
||||||
|
def read_bits(n):
|
||||||
|
nonlocal pos
|
||||||
|
val = 0
|
||||||
|
for _ in range(n):
|
||||||
|
val = (val << 1) | bits[pos]
|
||||||
|
pos += 1
|
||||||
|
return val
|
||||||
|
|
||||||
|
code_bits = 9
|
||||||
|
prev = read_bits(code_bits)
|
||||||
|
result.append(prev)
|
||||||
|
prev_bytes = dictionary[prev]
|
||||||
|
|
||||||
|
while pos < len(bits):
|
||||||
|
code = read_bits(code_bits)
|
||||||
|
if code in dictionary:
|
||||||
|
entry = dictionary[code]
|
||||||
|
elif code == dict_size:
|
||||||
|
entry = prev_bytes + prev_bytes[:1]
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
result.extend(entry)
|
||||||
|
dictionary[dict_size] = prev_bytes + entry[:1]
|
||||||
|
dict_size += 1
|
||||||
|
if dict_size >= (1 << code_bits) and code_bits < 12:
|
||||||
|
code_bits += 1
|
||||||
|
prev_bytes = entry
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def mtf_decode(data: list[int]) -> list[int]:
|
||||||
|
"""Move-to-front decoding."""
|
||||||
|
alphabet = list(range(256))
|
||||||
|
result = []
|
||||||
|
for code in data:
|
||||||
|
if code < len(alphabet):
|
||||||
|
val = alphabet[code]
|
||||||
|
result.append(val)
|
||||||
|
alphabet.pop(code)
|
||||||
|
alphabet.insert(0, val)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def bwt_inverse(data: list[int], primary_index: int) -> bytes:
|
||||||
|
"""Inverse Burrows-Wheeler transform."""
|
||||||
|
if not data:
|
||||||
|
return b''
|
||||||
|
n = len(data)
|
||||||
|
# Count occurrences
|
||||||
|
counts = [0] * 256
|
||||||
|
for b in data:
|
||||||
|
counts[b] += 1
|
||||||
|
|
||||||
|
# Compute cumulative counts (start positions)
|
||||||
|
start = [0] * 256
|
||||||
|
total = 0
|
||||||
|
for i in range(256):
|
||||||
|
start[i] = total
|
||||||
|
total += counts[i]
|
||||||
|
|
||||||
|
# Build next array
|
||||||
|
next_idx = [0] * n
|
||||||
|
temp_count = [0] * 256
|
||||||
|
for i in range(n):
|
||||||
|
b = data[i]
|
||||||
|
next_idx[i] = start[b] + temp_count[b]
|
||||||
|
temp_count[b] += 1
|
||||||
|
|
||||||
|
# Reconstruct
|
||||||
|
result = bytearray()
|
||||||
|
idx = primary_index
|
||||||
|
for _ in range(n):
|
||||||
|
idx = next_idx[idx]
|
||||||
|
result.append(data[idx])
|
||||||
|
|
||||||
|
return bytes(result)
|
||||||
|
|
||||||
|
|
||||||
|
def decompress(path: Path) -> dict:
|
||||||
|
"""Decompress an equation database file."""
|
||||||
|
raw = path.read_bytes()
|
||||||
|
|
||||||
|
# Read header
|
||||||
|
offset = 0
|
||||||
|
header_len = struct.unpack('>I', raw[offset:offset+4])[0]
|
||||||
|
offset += 4
|
||||||
|
header = json.loads(raw[offset:offset+header_len].decode())
|
||||||
|
offset += header_len
|
||||||
|
|
||||||
|
chain = header.get("chain", ["bwt", "mtf", "lzw"])
|
||||||
|
meta = header.get("metadata", {})
|
||||||
|
compressed = raw[offset:]
|
||||||
|
|
||||||
|
# Apply decompression steps in reverse order
|
||||||
|
data = compressed
|
||||||
|
|
||||||
|
# Step 1: LZW decode
|
||||||
|
lzw_meta = meta.get("lzw", {})
|
||||||
|
integers = lzw_decode(data, lzw_meta.get("max_dict", 4096))
|
||||||
|
|
||||||
|
# Step 2: MTF decode
|
||||||
|
integers = mtf_decode(integers)
|
||||||
|
|
||||||
|
# Step 3: BWT inverse
|
||||||
|
bwt_meta = meta.get("bwt", {})
|
||||||
|
primary = bwt_meta.get("primary_index", 0)
|
||||||
|
raw_bytes = bwt_inverse(integers, primary)
|
||||||
|
|
||||||
|
# Parse as JSON
|
||||||
|
return json.loads(raw_bytes.decode("utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
path = Path(sys.argv[1]) if len(sys.argv) > 1 else \
|
||||||
|
"/home/allaun/Research Stack/3-Mathematical-Models/equations_compressed/chain_equations_20260504_134248.compressed"
|
||||||
|
|
||||||
|
print(f"Decompressing {path}...")
|
||||||
|
data = decompress(path)
|
||||||
|
|
||||||
|
if isinstance(data, dict):
|
||||||
|
print(f"Type: dict with {len(data)} keys")
|
||||||
|
for k, v in data.items():
|
||||||
|
if isinstance(v, list):
|
||||||
|
print(f" {k}: list[{len(v)}]")
|
||||||
|
if len(v) > 0:
|
||||||
|
print(f" first: {str(v[0])[:200]}")
|
||||||
|
elif isinstance(v, dict):
|
||||||
|
print(f" {k}: dict with {len(v)} keys")
|
||||||
|
else:
|
||||||
|
print(f" {k}: {type(v).__name__} = {str(v)[:200]}")
|
||||||
|
elif isinstance(data, list):
|
||||||
|
print(f"Type: list[{len(data)}]")
|
||||||
|
if len(data) > 0:
|
||||||
|
print(f" first: {str(data[0])[:200]}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
127
scripts/deploy_anytype.py
Normal file
127
scripts/deploy_anytype.py
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Deploy Anytype Sync Server on neon-64gb."""
|
||||||
|
import subprocess, os, tempfile, json
|
||||||
|
|
||||||
|
HOST = "allaun@100.92.88.64"
|
||||||
|
|
||||||
|
def sh(cmd, timeout=60):
|
||||||
|
r = subprocess.run(["ssh", "-o", "ConnectTimeout=10", HOST, cmd],
|
||||||
|
capture_output=True, text=True, timeout=timeout)
|
||||||
|
out = r.stdout.strip()[:300]
|
||||||
|
err = r.stderr.strip()[:200]
|
||||||
|
if out: print(f" {out}")
|
||||||
|
if err: print(f" ! {err}")
|
||||||
|
return r.returncode == 0
|
||||||
|
|
||||||
|
def scp_put(content, remote_path):
|
||||||
|
with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
|
||||||
|
f.write(content)
|
||||||
|
lpath = f.name
|
||||||
|
subprocess.run(["scp", lpath, f"{HOST}:{remote_path}"],
|
||||||
|
capture_output=True, timeout=30)
|
||||||
|
os.unlink(lpath)
|
||||||
|
|
||||||
|
print("=== 1. Install any-sync-network ===")
|
||||||
|
sh("""
|
||||||
|
curl -sL https://github.com/anyproto/any-sync-tools/releases/download/v0.6.1/any-sync-tools-0.6.1.linux-arm64.tgz -o /tmp/any-sync-tools.tgz
|
||||||
|
tar xzf /tmp/any-sync-tools.tgz -C /tmp/
|
||||||
|
sudo cp /tmp/any-sync-network /usr/local/bin/ 2>/dev/null || cp /tmp/any-sync-network ~/.local/bin/ 2>/dev/null || true
|
||||||
|
export PATH=$PATH:$HOME/.local/bin
|
||||||
|
which any-sync-network
|
||||||
|
""")
|
||||||
|
|
||||||
|
print("=== 2. Generate configs ===")
|
||||||
|
sh("mkdir -p /home/allaun/any-sync-config")
|
||||||
|
|
||||||
|
default_template = """external-addresses:
|
||||||
|
- 100.92.88.64
|
||||||
|
|
||||||
|
any-sync-coordinator:
|
||||||
|
listen: 0.0.0.0
|
||||||
|
yamuxPort: 4830
|
||||||
|
quicPort: 5830
|
||||||
|
mongo:
|
||||||
|
connect: mongodb://localhost:27017
|
||||||
|
database: coordinator
|
||||||
|
defaultLimits:
|
||||||
|
spaceMembersRead: 1000
|
||||||
|
spaceMembersWrite: 1000
|
||||||
|
sharedSpacesLimit: 1000
|
||||||
|
|
||||||
|
any-sync-consensusnode:
|
||||||
|
listen: 0.0.0.0
|
||||||
|
yamuxPort: 4530
|
||||||
|
quicPort: 5530
|
||||||
|
mongo:
|
||||||
|
connect: mongodb://localhost:27017/?w=majority
|
||||||
|
database: consensus
|
||||||
|
|
||||||
|
any-sync-filenode:
|
||||||
|
listen: 0.0.0.0
|
||||||
|
yamuxPort: 4730
|
||||||
|
quicPort: 5730
|
||||||
|
s3Store:
|
||||||
|
endpoint: http://localhost:9000
|
||||||
|
bucket: anytype
|
||||||
|
indexBucket: anytype-index
|
||||||
|
region: us-east-1
|
||||||
|
profile: default
|
||||||
|
forcePathStyle: true
|
||||||
|
redis:
|
||||||
|
url: redis://localhost:6379?dial_timeout=3&read_timeout=6s
|
||||||
|
defaultLimit: 1099511627776
|
||||||
|
|
||||||
|
any-sync-node:
|
||||||
|
listen:
|
||||||
|
- 0.0.0.0
|
||||||
|
yamuxPort:
|
||||||
|
- 4430
|
||||||
|
quicPort:
|
||||||
|
- 5430
|
||||||
|
"""
|
||||||
|
|
||||||
|
scp_put(default_template, "/home/allaun/any-sync-config/defaultTemplate.yml")
|
||||||
|
|
||||||
|
print("=== 3. Generate auto config ===")
|
||||||
|
sh("""
|
||||||
|
export PATH=$PATH:$HOME/.local/bin
|
||||||
|
any-sync-network create --auto --conf /home/allaun/any-sync-config/defaultTemplate.yml --dir /home/allaun/any-sync-config 2>&1 || true
|
||||||
|
ls -la /home/allaun/any-sync-config/
|
||||||
|
""")
|
||||||
|
|
||||||
|
print("=== 4. Start MongoDB (replica set) ===")
|
||||||
|
sh("""
|
||||||
|
podman rm -f anytype-mongo 2>/dev/null
|
||||||
|
podman run -d --name anytype-mongo --network host \
|
||||||
|
docker.io/mongo:7 --replSet rs0 --bind_ip 0.0.0.0 2>&1
|
||||||
|
""")
|
||||||
|
|
||||||
|
subprocess.run(["sleep", "10"])
|
||||||
|
|
||||||
|
print("=== 5. Init MongoDB replica set ===")
|
||||||
|
sh('''
|
||||||
|
podman exec anytype-mongo mongosh --quiet --eval 'rs.initiate({_id:"rs0",members:[{_id:0,host:"localhost:27017"}]})' 2>&1 | head -5
|
||||||
|
''')
|
||||||
|
|
||||||
|
print("=== 6. Start Coordinator ===")
|
||||||
|
sh("""
|
||||||
|
podman rm -f any-sync-coordinator 2>/dev/null
|
||||||
|
podman run -d --name any-sync-coordinator --network host \
|
||||||
|
-v /home/allaun/any-sync-config:/etc/any-sync:ro \
|
||||||
|
docker.io/anyproto/any-sync-coordinator:v0.9.1
|
||||||
|
""")
|
||||||
|
|
||||||
|
print("=== 7. Start Sync Node ===")
|
||||||
|
sh("""
|
||||||
|
podman rm -f any-sync-node-1 2>/dev/null
|
||||||
|
podman run -d --name any-sync-node-1 --network host \
|
||||||
|
-v /home/allaun/any-sync-config:/etc/any-sync:ro \
|
||||||
|
docker.io/anyproto/any-sync-node:v0.11.1
|
||||||
|
""")
|
||||||
|
|
||||||
|
subprocess.run(["sleep", "10"])
|
||||||
|
|
||||||
|
print("=== Status ===")
|
||||||
|
sh("podman ps --format 'table {{.Names}}\t{{.Status}}'")
|
||||||
|
sh("podman logs any-sync-coordinator 2>&1 | tail -3")
|
||||||
|
sh("podman logs any-sync-node-1 2>&1 | tail -3")
|
||||||
92
scripts/fix_appflowy.py
Normal file
92
scripts/fix_appflowy.py
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Last-resort AppFloyo fix — runs all commands directly via SSH."""
|
||||||
|
import subprocess, time
|
||||||
|
|
||||||
|
HOST = "allaun@100.92.88.64"
|
||||||
|
|
||||||
|
def sh(cmd, timeout=60):
|
||||||
|
r = subprocess.run(["ssh", "-o", "ConnectTimeout=10", HOST, cmd],
|
||||||
|
capture_output=True, text=True, timeout=timeout)
|
||||||
|
if r.stdout.strip():
|
||||||
|
print(r.stdout.strip()[:200])
|
||||||
|
if r.returncode != 0:
|
||||||
|
err = r.stderr.strip()[:100]
|
||||||
|
if err:
|
||||||
|
print(f" (stderr: {err})")
|
||||||
|
|
||||||
|
def run(cmd, timeout=60):
|
||||||
|
return subprocess.run(["ssh", "-o", "ConnectTimeout=10", HOST, cmd],
|
||||||
|
capture_output=True, text=True, timeout=timeout).returncode
|
||||||
|
|
||||||
|
print("1. Postgres")
|
||||||
|
run("podman restart arxiv-pg 2>/dev/null", timeout=15)
|
||||||
|
time.sleep(10)
|
||||||
|
run("pg_isready -h localhost", timeout=10)
|
||||||
|
|
||||||
|
print("2. AppFloyo DB + Redis")
|
||||||
|
sh("podman exec arxiv-pg psql -U postgres -c 'CREATE DATABASE appflowy' 2>/dev/null || true", timeout=15)
|
||||||
|
sh("podman exec arxiv-pg psql -U postgres -d appflowy -c 'CREATE SCHEMA IF NOT EXISTS auth' 2>/dev/null", timeout=10)
|
||||||
|
run("podman run -d --name redis --network host docker.io/library/redis:7-alpine 2>/dev/null", timeout=20)
|
||||||
|
|
||||||
|
print("3. GoTrue init migration")
|
||||||
|
init_sql = subprocess.run(
|
||||||
|
["podman", "run", "--rm", "docker.io/appflowyinc/gotrue:0.15.1", "cat", "/migrations/00_init_auth_schema.up.sql"],
|
||||||
|
capture_output=True, text=True, timeout=15
|
||||||
|
).stdout.replace("{{ index .Options \"Namespace\" }}", "auth")
|
||||||
|
|
||||||
|
proc = subprocess.Popen(
|
||||||
|
["ssh", HOST, "podman exec -i arxiv-pg psql -U postgres -d appflowy -v ON_ERROR_STOP=1"],
|
||||||
|
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
|
)
|
||||||
|
stdout, stderr = proc.communicate(input=init_sql.encode(), timeout=60)
|
||||||
|
print(f" Init: {stdout.decode()[:100] if stdout else 'ok'}")
|
||||||
|
|
||||||
|
print("4. Mark all GoTrue migrations done")
|
||||||
|
migs = subprocess.run(
|
||||||
|
["podman", "run", "--rm", "docker.io/appflowyinc/gotrue:0.15.1", "ls", "/migrations/"],
|
||||||
|
capture_output=True, text=True, timeout=15
|
||||||
|
)
|
||||||
|
versions = sorted(set(f.split("_")[0] for f in migs.stdout.strip().split("\n") if f.endswith(".up.sql")))
|
||||||
|
|
||||||
|
for v in versions:
|
||||||
|
run(f"podman exec arxiv-pg psql -U postgres -d appflowy -c \"INSERT INTO auth.schema_migrations (version) VALUES ('{v}') ON CONFLICT DO NOTHING\"", timeout=10)
|
||||||
|
|
||||||
|
ct = run("podman exec arxiv-pg psql -U postgres -d appflowy -t -A -c 'SELECT COUNT(*) FROM auth.schema_migrations'", timeout=10)
|
||||||
|
print(f" {len(versions)} versions, tracked")
|
||||||
|
|
||||||
|
print("5. Pre-create one_time_tokens")
|
||||||
|
subprocess.Popen(
|
||||||
|
["ssh", HOST, "podman exec -i arxiv-pg psql -U postgres -d appflowy"],
|
||||||
|
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE
|
||||||
|
).communicate(
|
||||||
|
input=b"CREATE TABLE IF NOT EXISTS auth.one_time_tokens (id uuid PRIMARY KEY, user_id uuid, token_type text NOT NULL, token_hash text NOT NULL, expires_at timestamptz NOT NULL, created_at timestamptz NOT NULL DEFAULT now());",
|
||||||
|
timeout=30
|
||||||
|
)
|
||||||
|
|
||||||
|
print("6. Start GoTrue")
|
||||||
|
run("podman rm -f appflowy_gotrue_1 2>/dev/null", timeout=10)
|
||||||
|
sh(
|
||||||
|
'podman run -d --name appflowy_gotrue_1 --network host '
|
||||||
|
'-e GOTRUE_ADMIN_EMAIL=admin@researchstack.info '
|
||||||
|
'-e GOTRUE_ADMIN_PASSWORD=admin123 '
|
||||||
|
'-e GOTRUE_DISABLE_SIGNUP=false '
|
||||||
|
'-e GOTRUE_SITE_URL=https://researchstack.info/appflowy '
|
||||||
|
'-e GOTRUE_URI_ALLOW_LIST=** '
|
||||||
|
'-e GOTRUE_JWT_SECRET=jwt_ '
|
||||||
|
'-e GOTRUE_JWT_EXP=3600 '
|
||||||
|
'-e GOTRUE_DB_DRIVER=postgres '
|
||||||
|
'-e API_EXTERNAL_URL=https://researchstack.info/appflowy/gotrue '
|
||||||
|
"-e DATABASE_URL='postgres://postgres:postgres@localhost:5432/appflowy' "
|
||||||
|
'-e PORT=9999 '
|
||||||
|
'-e GOTRUE_MAILER_AUTOCONFIRM=true '
|
||||||
|
'-e GOTRUE_LOG_LEVEL=info '
|
||||||
|
'-e REDIS_ENABLED=true '
|
||||||
|
'-e REDIS_URL=redis://localhost:6379 '
|
||||||
|
'docker.io/appflowyinc/gotrue:0.15.1', timeout=20
|
||||||
|
)
|
||||||
|
time.sleep(10)
|
||||||
|
|
||||||
|
hc = subprocess.run(["ssh", "-o", "ConnectTimeout=5", HOST,
|
||||||
|
"curl -s -o /dev/null -w '%{http_code}' http://localhost:9999/health"],
|
||||||
|
capture_output=True, text=True, timeout=10)
|
||||||
|
print(f" GoTrue: {hc.stdout.strip()}")
|
||||||
34
scripts/fix_appflowy.sh
Normal file
34
scripts/fix_appflowy.sh
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
PATH=/home/allaun/.nix-profile/bin:/usr/bin:/bin
|
||||||
|
export PGPASSWORD=postgres
|
||||||
|
|
||||||
|
echo "=== Creating SQLx migration table ==="
|
||||||
|
PGPASSWORD=postgres psql -h localhost -U postgres -d appflowy -f /tmp/fix_appflowy.sql 2>&1 || true
|
||||||
|
|
||||||
|
COUNT=$(PGPASSWORD=postgres psql -h localhost -U postgres -d appflowy -t -A -c 'SELECT COUNT(*) FROM auth._sqlx_migrations' 2>/dev/null)
|
||||||
|
echo "SQLx migrations tracked: $COUNT"
|
||||||
|
|
||||||
|
echo "=== Starting AppFloyo ==="
|
||||||
|
podman rm -f appflowy_cloud 2>/dev/null || true
|
||||||
|
podman run -d --name appflowy_cloud \
|
||||||
|
--network host \
|
||||||
|
-e APPFLOWY_DATABASE_URL="postgres://postgres:postgres@localhost:5432/appflowy?sslmode=disable&options=-c%20search_path=public" \
|
||||||
|
-e APPFLOWY_ENVIRONMENT=production \
|
||||||
|
-e APPFLOWY_REDIS_URI=redis://localhost:6379 \
|
||||||
|
-e APPFLOWY_GOTRUE_JWT_SECRET=jwt_ \
|
||||||
|
-e APPFLOWY_GOTRUE_JWT_EXP=3600 \
|
||||||
|
-e APPFLOWY_GOTRUE_BASE_URL=http://localhost:9999 \
|
||||||
|
-e APPFLOWY_BASE_URL=https://researchstack.info/appflowy \
|
||||||
|
-e APPFLOWY_WEB_URL=https://researchstack.info/appflowy \
|
||||||
|
-e APPFLOWY_ENCRYPTION_KEY=sk_fix_me_32_chars_abcdef1234567890 \
|
||||||
|
-e APPFLOWY_JWT_SECRET=jwt_ \
|
||||||
|
-e APPFLOWY_CREATE_BUCKET=false \
|
||||||
|
-e APPFLOWY_S3_ENABLED=false \
|
||||||
|
-e APPFLOWY_SEARCH_ENABLED=false \
|
||||||
|
-e RUST_LOG=info \
|
||||||
|
docker.io/appflowyinc/appflowy_cloud:latest
|
||||||
|
|
||||||
|
echo "=== Done. Checking health in 30s ==="
|
||||||
|
sleep 30
|
||||||
|
curl -s -o /dev/null -w "AppFloyo: %{http_code}\n" http://localhost:8000/api/health 2>/dev/null
|
||||||
35
scripts/neon-stack.sh
Normal file
35
scripts/neon-stack.sh
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
podman rm -f $(podman ps -aq) 2>/dev/null || true
|
||||||
|
|
||||||
|
podman run -d --name postgres --network host --restart always \
|
||||||
|
-e POSTGRES_PASSWORD=postgres \
|
||||||
|
docker.io/library/postgres:16 -c max_connections=200
|
||||||
|
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
pg_isready -h localhost -q && break
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
psql -h localhost -U postgres -c "CREATE DATABASE authentik" 2>/dev/null || true
|
||||||
|
psql -h localhost -U postgres -d authentik -c "CREATE EXTENSION IF NOT EXISTS citext" 2>/dev/null || true
|
||||||
|
|
||||||
|
podman run -d --name redis --network host --restart always docker.io/library/redis:7-alpine
|
||||||
|
|
||||||
|
for role in server worker; do
|
||||||
|
podman run -d --name "authentik-$role" --network host --restart always \
|
||||||
|
-e AUTHENTIK_SECRET_KEY="authentik-secret-key" \
|
||||||
|
-e AUTHENTIK_POSTGRESQL__HOST=localhost \
|
||||||
|
-e AUTHENTIK_POSTGRESQL__NAME=authentik \
|
||||||
|
-e AUTHENTIK_POSTGRESQL__USER=postgres \
|
||||||
|
-e AUTHENTIK_POSTGRESQL__PASSWORD=postgres \
|
||||||
|
-e AUTHENTIK_REDIS__HOST=localhost \
|
||||||
|
-e AUTHENTIK_REDIS__PORT=6379 \
|
||||||
|
ghcr.io/goauthentik/server:2026.5.3 "$role"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Stack started"
|
||||||
|
podman ps --format "table {{.Names}}\t{{.Status}}"
|
||||||
|
echo "Authentik: http://localhost:9000"
|
||||||
|
loginctl enable-linger
|
||||||
13
scripts/stack/.env
Normal file
13
scripts/stack/.env
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
PG_PASS=postgres
|
||||||
|
AUTHENTIK_SECRET_KEY=authentik-secret-key
|
||||||
|
AUTHENTIK_TAG=2026.5.3
|
||||||
|
PG_USER=postgres
|
||||||
|
PG_DB=authentik
|
||||||
|
PORT_HTTP=9000
|
||||||
|
PORT_HTTPS=9443
|
||||||
|
PORT_HOMARR=7575
|
||||||
|
PORT_CADDY_HTTP=9090
|
||||||
|
PORT_CADDY_HTTPS=9091
|
||||||
|
AUTHENTIK_API_KEY=9LyRvCaRLSmDrkGxQ45xRKt0JrBWMe3tI3sV2x0HV30mMcxaDBEA4cJ8QDZ2
|
||||||
|
FORGEJO_API_KEY=4b8b95c15ba8a69358e2547244dad1b04fedec66
|
||||||
|
FORGEJO_API_KEY=858fe52d74764f20f23743a8d04544b95212985d
|
||||||
6
scripts/stack/caddy/Caddyfile
Normal file
6
scripts/stack/caddy/Caddyfile
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
localhost:80 {
|
||||||
|
respond "Neon Stack running"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Authentik: reverse_proxy authentik-server:9000
|
||||||
|
# Homarr: reverse_proxy homarr:7575
|
||||||
165
scripts/stack/compose.yml
Normal file
165
scripts/stack/compose.yml
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: docker.io/library/postgres:16-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: ${PG_PASS:?required}
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: docker.io/library/redis:7-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
|
||||||
|
authentik-server:
|
||||||
|
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2026.5.3}
|
||||||
|
command: server
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${PORT_HTTP:-9000}:9000"
|
||||||
|
- "${PORT_HTTPS:-9443}:9443"
|
||||||
|
environment:
|
||||||
|
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-authentik-secret-key}
|
||||||
|
AUTHENTIK_POSTGRESQL__HOST: postgres
|
||||||
|
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
|
||||||
|
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-postgres}
|
||||||
|
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS:-postgres}
|
||||||
|
AUTHENTIK_REDIS__HOST: redis
|
||||||
|
AUTHENTIK_REDIS__PORT: 6379
|
||||||
|
volumes:
|
||||||
|
- authentik_data:/data
|
||||||
|
- ./authentik/templates:/templates
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_started
|
||||||
|
redis:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
|
authentik-worker:
|
||||||
|
image: ghcr.io/goauthentik/server:${AUTHENTIK_TAG:-2026.5.3}
|
||||||
|
command: worker
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:-authentik-secret-key}
|
||||||
|
AUTHENTIK_POSTGRESQL__HOST: postgres
|
||||||
|
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
|
||||||
|
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-postgres}
|
||||||
|
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS:-postgres}
|
||||||
|
AUTHENTIK_REDIS__HOST: redis
|
||||||
|
AUTHENTIK_REDIS__PORT: 6379
|
||||||
|
volumes:
|
||||||
|
- authentik_data:/data
|
||||||
|
- ./authentik/templates:/templates
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_started
|
||||||
|
redis:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
|
homarr:
|
||||||
|
image: ghcr.io/homarr-labs/homarr:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${PORT_HOMARR:-7575}:7575"
|
||||||
|
environment:
|
||||||
|
- SECRET_ENCRYPTION_KEY=${SECRET_ENCRYPTION_KEY:-1502ee91fadf86c4c99a9bc1a66ce17ac9ab3a148edbe4084b4af496031ae10d}
|
||||||
|
- HOMARR__POSTGRES_ENABLED=true
|
||||||
|
- HOMARR__POSTGRES_HOST=postgres
|
||||||
|
- HOMARR__POSTGRES_PORT=5432
|
||||||
|
- HOMARR__POSTGRES_USER=${PG_USER:-postgres}
|
||||||
|
- HOMARR__POSTGRES_PASSWORD=${PG_PASS:?required}
|
||||||
|
- HOMARR__POSTGRES_DATABASE=homarr
|
||||||
|
volumes:
|
||||||
|
- homarr_data:/app/data
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
|
audiobookshelf:
|
||||||
|
image: ghcr.io/advplyr/audiobookshelf:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${PORT_ABS:-13378}:80"
|
||||||
|
volumes:
|
||||||
|
- abs_config:/config
|
||||||
|
- abs_metadata:/metadata
|
||||||
|
- ./audiobooks:/audiobooks:ro
|
||||||
|
- ./podcasts:/podcasts:ro
|
||||||
|
depends_on: []
|
||||||
|
|
||||||
|
kanboard:
|
||||||
|
image: kanboard/kanboard:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${PORT_KANBOARD:-8090}:80"
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=postgres://postgres:postgres@postgres:5432/kanboard?sslmode=disable
|
||||||
|
volumes:
|
||||||
|
- kanboard_data:/var/www/app/data
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
|
forgejo:
|
||||||
|
image: codeberg.org/forgejo/forgejo:1.20.2-0
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${PORT_FORGEJO_HTTP:-3000}:3000"
|
||||||
|
- "${PORT_FORGEJO_SSH:-2222}:22"
|
||||||
|
volumes:
|
||||||
|
- forgejo_data:/data
|
||||||
|
environment:
|
||||||
|
- USER_UID=1000
|
||||||
|
- USER_GID=100
|
||||||
|
- FORGEJO__database__DB_TYPE=postgres
|
||||||
|
- FORGEJO__database__HOST=postgres:5432
|
||||||
|
- FORGEJO__database__NAME=forgejo
|
||||||
|
- FORGEJO__database__USER=postgres
|
||||||
|
- FORGEJO__database__PASSWD=postgres
|
||||||
|
- FORGEJO__actions__ENABLED=true
|
||||||
|
- FORGEJO__server__DOMAIN=neon-64gb
|
||||||
|
- FORGEJO__server__HTTP_PORT=3000
|
||||||
|
- FORGEJO__server__ROOT_URL=http://localhost:3000
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
|
webhook:
|
||||||
|
image: docker.io/library/python:3.13-alpine
|
||||||
|
command: python3 /app/webhook_receiver.py
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${PORT_WEBHOOK:-8080}:8080"
|
||||||
|
environment:
|
||||||
|
- WEBHOOK_SECRET=${WEBHOOK_SECRET:-auto-pipeline-secret}
|
||||||
|
- WEBHOOK_PORT=8080
|
||||||
|
volumes:
|
||||||
|
- "${PIPELINE_DIR:-/home/allaun/SilverSight}/scripts/auto:/app:ro"
|
||||||
|
depends_on: []
|
||||||
|
|
||||||
|
caddy:
|
||||||
|
image: docker.io/library/caddy:2-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${PORT_CADDY_HTTP:-9090}:80"
|
||||||
|
- "${PORT_CADDY_HTTPS:-9091}:443"
|
||||||
|
volumes:
|
||||||
|
- ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||||
|
- caddy_data:/data
|
||||||
|
depends_on:
|
||||||
|
- authentik-server
|
||||||
|
- homarr
|
||||||
|
- audiobookshelf
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
redis_data:
|
||||||
|
authentik_data:
|
||||||
|
homarr_data:
|
||||||
|
abs_config:
|
||||||
|
abs_metadata:
|
||||||
|
kanboard_data:
|
||||||
|
forgejo_data:
|
||||||
|
caddy_data:
|
||||||
32
scripts/stack/setup_homarr.py
Normal file
32
scripts/stack/setup_homarr.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Find Authentik API paths and create Homarr OAuth2 provider."""
|
||||||
|
import urllib.request, json, sys, re
|
||||||
|
|
||||||
|
AK = "9LyRvCaRLSmDrkGxQ45xRKt0JrBWMe3tI3sV2x0HV30mMcxaDBEA4cJ8QDZ2"
|
||||||
|
BASE = "http://localhost:9000"
|
||||||
|
|
||||||
|
def api(method, path, data=None):
|
||||||
|
req = urllib.request.Request(f"{BASE}{path}",
|
||||||
|
data=json.dumps(data).encode() if data else None,
|
||||||
|
headers={"Authorization": f"Bearer {AK}", "Content-Type": "application/json"},
|
||||||
|
method=method)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req) as r:
|
||||||
|
return json.loads(r.read())
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
body = e.read().decode()[:300]
|
||||||
|
print(f" {method} {path}: {e.code} {body}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Get API browser HTML to find paths
|
||||||
|
req = urllib.request.Request(f"{BASE}/api/v3/",
|
||||||
|
headers={"Authorization": f"Bearer {AK}"})
|
||||||
|
with urllib.request.urlopen(req) as r:
|
||||||
|
html = r.read().decode()
|
||||||
|
|
||||||
|
# Find all API paths from the browser
|
||||||
|
paths = set(re.findall(r'href="([^"]+)"', html))
|
||||||
|
api_paths = [p for p in paths if '/api/v3/' in p]
|
||||||
|
print("API paths found:")
|
||||||
|
for p in sorted(api_paths):
|
||||||
|
print(f" {p}")
|
||||||
63
scripts/stack/setup_homarr_auth.py
Normal file
63
scripts/stack/setup_homarr_auth.py
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Create Authentik OAuth2 provider + application for Homarr."""
|
||||||
|
import urllib.request, json, sys
|
||||||
|
|
||||||
|
AK = "9LyRvCaRLSmDrkGxQ45xRKt0JrBWMe3tI3sV2x0HV30mMcxaDBEA4cJ8QDZ2"
|
||||||
|
BASE = "http://localhost:9000"
|
||||||
|
|
||||||
|
def api(method, path, data=None):
|
||||||
|
req = urllib.request.Request(f"{BASE}{path}",
|
||||||
|
data=json.dumps(data).encode() if data else None,
|
||||||
|
headers={"Authorization": f"Bearer {AK}", "Content-Type": "application/json"},
|
||||||
|
method=method)
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(req) as r:
|
||||||
|
return json.loads(r.read())
|
||||||
|
except urllib.error.HTTPError as e:
|
||||||
|
print(f" {method} {path}: {e.code} {e.read().decode()[:200]}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 1. Get authorization flow
|
||||||
|
flows = api("GET", "/api/v3/flows/?slug=provider-authorization-implicit-consent")
|
||||||
|
if not flows or not flows.get("results"):
|
||||||
|
# Try alternative
|
||||||
|
flows = api("GET", "/api/v3/flows/")
|
||||||
|
if flows:
|
||||||
|
print(f"Available flows: {[(f['slug'], f['name']) for f in flows.get('results',[])]}")
|
||||||
|
flow_pk = flows["results"][0]["pk"]
|
||||||
|
else:
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
flow_pk = flows["results"][0]["pk"]
|
||||||
|
print(f"Flow PK: {flow_pk}")
|
||||||
|
|
||||||
|
# 2. Get provider type
|
||||||
|
provider_types = api("GET", "/api/v3/providers/all/types/")
|
||||||
|
print(f"Provider types: {provider_types}")
|
||||||
|
|
||||||
|
# 3. Create OAuth2 provider
|
||||||
|
provider = api("POST", "/api/v3/providers/oauth2/", {
|
||||||
|
"name": "Homarr",
|
||||||
|
"authorization_flow": flow_pk,
|
||||||
|
"client_type": "confidential",
|
||||||
|
"redirect_uris": "http://localhost:7575/api/auth/callback/authentik"
|
||||||
|
})
|
||||||
|
if provider:
|
||||||
|
print(f"Provider created: {provider.get('pk')} client_id={provider.get('client_id')}")
|
||||||
|
client_id = provider.get("client_id")
|
||||||
|
client_secret = provider.get("client_secret")
|
||||||
|
|
||||||
|
# 4. Create Application
|
||||||
|
app = api("POST", "/api/v3/core/applications/", {
|
||||||
|
"name": "Homarr",
|
||||||
|
"slug": "homarr",
|
||||||
|
"provider": provider["pk"]
|
||||||
|
})
|
||||||
|
if app:
|
||||||
|
print(f"Application created: {app.get('pk')} slug={app.get('slug')}")
|
||||||
|
print(f"\nHomarr OIDC config:")
|
||||||
|
print(f" Client ID: {client_id}")
|
||||||
|
print(f" Client Secret: {client_secret}")
|
||||||
|
print(f" Issuer: http://localhost:9000/application/o/homarr/")
|
||||||
|
else:
|
||||||
|
print("Failed to create application")
|
||||||
13
scripts/stack/webhook.service
Normal file
13
scripts/stack/webhook.service
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Auto-pipeline webhook receiver
|
||||||
|
After=network.target forgejo.service
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStart=/home/allaun/.nix-profile/bin/python3 /home/allaun/pipeline/auto/webhook_receiver.py
|
||||||
|
WorkingDirectory=/home/allaun/pipeline
|
||||||
|
Restart=always
|
||||||
|
RestartSec=5
|
||||||
|
User=allaun
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
331
tests/test_nuvmap_equivalence.py
Normal file
331
tests/test_nuvmap_equivalence.py
Normal file
|
|
@ -0,0 +1,331 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
NUVMAP Equivalence Test — SilverSight vs Research Stack Archive
|
||||||
|
|
||||||
|
Compares the Q16_16 SilverSight port against the original float-based
|
||||||
|
Research Stack implementation to verify numerical equivalence within
|
||||||
|
Q16_16 quantization tolerance.
|
||||||
|
|
||||||
|
Run: python -m tests.test_nuvmap_equivalence
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, "/home/allaun/Research Stack/5-Applications/cff")
|
||||||
|
sys.path.insert(0, "/home/allaun/SilverSight/python")
|
||||||
|
|
||||||
|
# Import original (float) from Research Stack
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
spec_orig = importlib.util.spec_from_file_location(
|
||||||
|
"original_nuvmap",
|
||||||
|
"/home/allaun/Research Stack/5-Applications/cff/nuvmap/projection_engine.py"
|
||||||
|
)
|
||||||
|
original_module = importlib.util.module_from_spec(spec_orig)
|
||||||
|
spec_orig.loader.exec_module(original_module)
|
||||||
|
|
||||||
|
OriginalEngine = original_module.NUVMAPProjectionEngine
|
||||||
|
OriginalCell = original_module.NUVMAPCell
|
||||||
|
OriginalSurface = original_module.NUVMAPSurface
|
||||||
|
original_build = original_module.build_nuvmap_from_eigenmass
|
||||||
|
|
||||||
|
# Import ported (Q16_16) from SilverSight
|
||||||
|
spec_ported = importlib.util.spec_from_file_location(
|
||||||
|
"ported_nuvmap",
|
||||||
|
"/home/allaun/SilverSight/python/nuvmap/projection_engine.py"
|
||||||
|
)
|
||||||
|
ported_module = importlib.util.module_from_spec(spec_ported)
|
||||||
|
spec_ported.loader.exec_module(ported_module)
|
||||||
|
|
||||||
|
PortedEngine = ported_module.NUVMAPProjectionEngine
|
||||||
|
PortedCell = ported_module.NUVMAPCell
|
||||||
|
PortedSurface = ported_module.NUVMAPSurface
|
||||||
|
ported_build = ported_module.build_nuvmap_from_eigenmass
|
||||||
|
|
||||||
|
# Q16_16 constants from ported module
|
||||||
|
Q16_ONE = ported_module.Q16_ONE
|
||||||
|
Q16_HALF = ported_module.Q16_HALF
|
||||||
|
Q16_PCT1 = ported_module.Q16_PCT1
|
||||||
|
Q16_PCT70 = ported_module.Q16_PCT70
|
||||||
|
Q16_PCT30 = ported_module.Q16_PCT30
|
||||||
|
Q16_150PCT = ported_module.Q16_150PCT
|
||||||
|
SCALE = ported_module.SCALE
|
||||||
|
|
||||||
|
def f2q(f: float) -> int:
|
||||||
|
"""Float → Q16_16 raw (external boundary only)."""
|
||||||
|
import math
|
||||||
|
if math.isnan(f) or math.isinf(f):
|
||||||
|
return 0
|
||||||
|
return max(-2147483648, min(2147483647, round(f * SCALE)))
|
||||||
|
|
||||||
|
def q2f(q: int) -> float:
|
||||||
|
"""Q16_16 raw → float (for comparison only)."""
|
||||||
|
return q / SCALE
|
||||||
|
|
||||||
|
|
||||||
|
# Test data — representative eigenmass inputs
|
||||||
|
TEST_DATA = [
|
||||||
|
{
|
||||||
|
"equation_id": 1,
|
||||||
|
"amvr": 0.8, "avmr": 0.75, "chiral_residual": 0.05,
|
||||||
|
"chiral_state": "achiral_stable",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"equation_id": 2,
|
||||||
|
"amvr": 0.3, "avmr": 0.4, "chiral_residual": 0.2,
|
||||||
|
"chiral_state": "left_handed_mass_bias",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"equation_id": 3,
|
||||||
|
"amvr": 0.1, "avmr": 0.15, "chiral_residual": 0.6,
|
||||||
|
"chiral_state": "chiral_scarred",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"equation_id": 4,
|
||||||
|
"amvr": 0.5, "avmr": 0.5, "chiral_residual": 0.1,
|
||||||
|
"chiral_state": "right_handed_vector_bias",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"equation_id": 5,
|
||||||
|
"amvr": 0.9, "avmr": 0.85, "chiral_residual": 0.02,
|
||||||
|
"chiral_state": "achiral_stable",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def convert_to_q16(data: list) -> list:
|
||||||
|
"""Convert float test data to Q16_16 for SilverSight engine."""
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"equation_id": d["equation_id"],
|
||||||
|
"amvr_q16": f2q(d["amvr"]),
|
||||||
|
"avmr_q16": f2q(d["avmr"]),
|
||||||
|
"chiral_residual_q16": f2q(d["chiral_residual"]),
|
||||||
|
"chiral_state": d["chiral_state"],
|
||||||
|
}
|
||||||
|
for d in data
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def compare_cells(orig: OriginalCell, ported: PortedCell, tol_q16: int = 2) -> bool:
|
||||||
|
"""Compare original float cell with ported Q16_16 cell."""
|
||||||
|
# Compare E_i
|
||||||
|
e_diff = abs(orig.E_i - q2f(ported.E_i))
|
||||||
|
e_tol = tol_q16 / SCALE
|
||||||
|
if e_diff > e_tol:
|
||||||
|
print(f" E_i mismatch: orig={orig.E_i:.6f}, ported={q2f(ported.E_i):.6f}, diff={e_diff:.6f} > {e_tol:.6f}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Compare R_i
|
||||||
|
r_diff = abs(orig.R_i - q2f(ported.R_i))
|
||||||
|
if r_diff > e_tol:
|
||||||
|
print(f" R_i mismatch: orig={orig.R_i:.6f}, ported={q2f(ported.R_i):.6f}, diff={r_diff:.6f}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Compare chi_i
|
||||||
|
chi_diff = abs(orig.chi_i - q2f(ported.chi_i))
|
||||||
|
if chi_diff > e_tol:
|
||||||
|
print(f" chi_i mismatch: orig={orig.chi_i:.6f}, ported={q2f(ported.chi_i):.6f}, diff={chi_diff:.6f}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Compare S_i
|
||||||
|
s_diff = abs(orig.S_i - q2f(ported.S_i))
|
||||||
|
if s_diff > e_tol:
|
||||||
|
print(f" S_i mismatch: orig={orig.S_i:.6f}, ported={q2f(ported.S_i):.6f}, diff={s_diff:.6f}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Compare L_i
|
||||||
|
l_diff = abs(orig.L_i - q2f(ported.L_i))
|
||||||
|
if l_diff > e_tol:
|
||||||
|
print(f" L_i mismatch: orig={orig.L_i:.6f}, ported={q2f(ported.L_i):.6f}, diff={l_diff:.6f}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Compare q_i (exact integer)
|
||||||
|
if orig.q_i != ported.q_i:
|
||||||
|
print(f" q_i mismatch: orig={orig.q_i}, ported={ported.q_i}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Compare admissible (exact boolean)
|
||||||
|
if orig.admissible != ported.admissible:
|
||||||
|
print(f" admissible mismatch: orig={orig.admissible}, ported={ported.admissible}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def compare_surfaces(orig: OriginalSurface, ported: PortedSurface, tol_q16: int = 2) -> bool:
|
||||||
|
"""Compare original and ported surfaces."""
|
||||||
|
if len(orig.cells) != len(ported.cells):
|
||||||
|
print(f"Cell count mismatch: orig={len(orig.cells)}, ported={len(ported.cells)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
ok = True
|
||||||
|
for i, (o, p) in enumerate(zip(orig.cells, ported.cells)):
|
||||||
|
if not compare_cells(o, p, tol_q16):
|
||||||
|
print(f"Cell {i} failed")
|
||||||
|
ok = False
|
||||||
|
|
||||||
|
# Compare surface-level metrics
|
||||||
|
if orig.total_qubits != ported.total_qubits:
|
||||||
|
print(f"total_qubits mismatch: orig={orig.total_qubits}, ported={ported.total_qubits}")
|
||||||
|
ok = False
|
||||||
|
|
||||||
|
# Bekenstein bound
|
||||||
|
b_diff = abs(orig.bekenstein_bound - q2f(ported.bekenstein_bound))
|
||||||
|
if b_diff > tol_q16 / SCALE:
|
||||||
|
print(f"bekenstein_bound mismatch: orig={orig.bekenstein_bound:.6f}, ported={q2f(ported.bekenstein_bound):.6f}")
|
||||||
|
ok = False
|
||||||
|
|
||||||
|
# Area utilization
|
||||||
|
au_diff = abs(orig.area_utilization - q2f(ported.area_utilization))
|
||||||
|
if au_diff > tol_q16 / SCALE:
|
||||||
|
print(f"area_utilization mismatch: orig={orig.area_utilization:.6f}, ported={q2f(ported.area_utilization):.6f}")
|
||||||
|
ok = False
|
||||||
|
|
||||||
|
return ok
|
||||||
|
|
||||||
|
|
||||||
|
def test_equivalence():
|
||||||
|
"""Main equivalence test."""
|
||||||
|
print("=" * 60)
|
||||||
|
print("NUVMAP Equivalence Test: Original (float) vs SilverSight (Q16_16)")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
# Convert test data
|
||||||
|
test_data_q16 = convert_to_q16(TEST_DATA)
|
||||||
|
|
||||||
|
# Run original (float) engine
|
||||||
|
print("\nRunning original Research Stack engine (float)...")
|
||||||
|
orig_engine = OriginalEngine(
|
||||||
|
total_qubit_budget=0,
|
||||||
|
chi_max=0.5,
|
||||||
|
R_max=0.5,
|
||||||
|
landauer_threshold=0.1,
|
||||||
|
)
|
||||||
|
orig_surface = orig_engine.project(TEST_DATA)
|
||||||
|
print(f" Cells: {len(orig_surface.cells)}, Qubits: {orig_surface.total_qubits}")
|
||||||
|
print(f" Bekenstein: {orig_surface.bekenstein_bound:.6f}, Area Util: {orig_surface.area_utilization:.6f}")
|
||||||
|
|
||||||
|
# Run ported (Q16_16) engine
|
||||||
|
print("\nRunning SilverSight engine (Q16_16)...")
|
||||||
|
ported_engine = PortedEngine(
|
||||||
|
total_qubit_budget=0,
|
||||||
|
chi_max_q16=Q16_HALF, # 0.5
|
||||||
|
R_max_q16=Q16_HALF, # 0.5
|
||||||
|
landauer_threshold_q16=Q16_ONE // 10, # 0.1
|
||||||
|
)
|
||||||
|
ported_surface = ported_engine.project(test_data_q16)
|
||||||
|
print(f" Cells: {len(ported_surface.cells)}, Qubits: {ported_surface.total_qubits}")
|
||||||
|
print(f" Bekenstein: {q2f(ported_surface.bekenstein_bound):.6f}, Area Util: {q2f(ported_surface.area_utilization):.6f}")
|
||||||
|
|
||||||
|
# Compare
|
||||||
|
print("\nComparing results...")
|
||||||
|
ok = compare_surfaces(orig_surface, ported_surface, tol_q16=2)
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
if ok:
|
||||||
|
print("✓ PASSED: SilverSight Q16_16 port matches original float results")
|
||||||
|
print(" (within ±2 Q16_16 steps = ±0.00003 float)")
|
||||||
|
else:
|
||||||
|
print("✗ FAILED: Significant numerical divergence detected")
|
||||||
|
print("=" * 60)
|
||||||
|
|
||||||
|
return ok
|
||||||
|
|
||||||
|
|
||||||
|
def test_cbor_roundtrip():
|
||||||
|
"""Test CBOR serialization round-trip."""
|
||||||
|
print("\nTesting CBOR serialization round-trip...")
|
||||||
|
|
||||||
|
test_data_q16 = convert_to_q16(TEST_DATA)
|
||||||
|
engine = PortedEngine(total_qubit_budget=100)
|
||||||
|
surface = engine.project(test_data_q16)
|
||||||
|
|
||||||
|
# Serialize
|
||||||
|
cbor_bytes = surface.to_cbor()
|
||||||
|
print(f" CBOR size: {len(cbor_bytes)} bytes")
|
||||||
|
|
||||||
|
# Deserialize
|
||||||
|
surface2 = PortedSurface.from_cbor(cbor_bytes)
|
||||||
|
|
||||||
|
# Compare
|
||||||
|
ok = compare_surfaces(surface, surface2, tol_q16=0)
|
||||||
|
if ok:
|
||||||
|
print(" ✓ CBOR round-trip successful")
|
||||||
|
else:
|
||||||
|
print(" ✗ CBOR round-trip failed")
|
||||||
|
return ok
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_roundtrip():
|
||||||
|
"""Test file I/O round-trip."""
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
|
||||||
|
print("\nTesting file I/O round-trip...")
|
||||||
|
|
||||||
|
test_data_q16 = convert_to_q16(TEST_DATA)
|
||||||
|
engine = PortedEngine(total_qubit_budget=100)
|
||||||
|
surface = engine.project(test_data_q16)
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(suffix='.cbor', delete=False) as f:
|
||||||
|
path = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
surface.to_file(path)
|
||||||
|
surface2 = PortedSurface.from_file(path)
|
||||||
|
|
||||||
|
ok = compare_surfaces(surface, surface2, tol_q16=0)
|
||||||
|
if ok:
|
||||||
|
print(" ✓ File round-trip successful")
|
||||||
|
else:
|
||||||
|
print(" ✗ File round-trip failed")
|
||||||
|
return ok
|
||||||
|
finally:
|
||||||
|
os.unlink(path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_admissibility_gate():
|
||||||
|
"""Test the quantum_storage_admissible gate matches."""
|
||||||
|
print("\nTesting quantum_storage_admissible gate...")
|
||||||
|
|
||||||
|
test_data_q16 = convert_to_q16(TEST_DATA)
|
||||||
|
|
||||||
|
orig_engine = OriginalEngine(chi_max=0.5, R_max=0.5, landauer_threshold=0.1)
|
||||||
|
orig_surface = orig_engine.project(TEST_DATA)
|
||||||
|
|
||||||
|
ported_engine = PortedEngine(
|
||||||
|
chi_max_q16=Q16_HALF,
|
||||||
|
R_max_q16=Q16_HALF,
|
||||||
|
landauer_threshold_q16=Q16_ONE // 10,
|
||||||
|
)
|
||||||
|
ported_surface = ported_engine.project(test_data_q16)
|
||||||
|
|
||||||
|
ok = True
|
||||||
|
for i in range(len(TEST_DATA)):
|
||||||
|
# Test with tau = 1.0
|
||||||
|
orig_result = orig_engine.quantum_storage_admissible(i, 1.0)
|
||||||
|
ported_result = ported_engine.quantum_storage_admissible(i, Q16_ONE)
|
||||||
|
if orig_result != ported_result:
|
||||||
|
print(f" Gate mismatch at cell {i}: orig={orig_result}, ported={ported_result}")
|
||||||
|
ok = False
|
||||||
|
|
||||||
|
if ok:
|
||||||
|
print(" ✓ Admissibility gate matches")
|
||||||
|
return ok
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
all_ok = True
|
||||||
|
all_ok &= test_equivalence()
|
||||||
|
all_ok &= test_cbor_roundtrip()
|
||||||
|
all_ok &= test_file_roundtrip()
|
||||||
|
all_ok &= test_admissibility_gate()
|
||||||
|
|
||||||
|
print("\n" + "=" * 60)
|
||||||
|
if all_ok:
|
||||||
|
print("ALL TESTS PASSED ✓")
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print("SOME TESTS FAILED ✗")
|
||||||
|
sys.exit(1)
|
||||||
Loading…
Add table
Reference in a new issue