Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
# Changelog

## Unreleased
## 0.3.1 — Security and stack alignment

- Overrode Stable Audio 3's upstream Torch 2.7.1 constraint with the locally
validated Torch and Torchaudio 2.10 pair, removing every fixable advisory
from the optional Python-provider dependency graph.
- Added an all-extras dependency audit and a dated security exception for the
two remaining upstream PyTorch findings in APIs GERM does not call directly.
- Updated Setuptools to 83.0.0 to close its Unicode-normalization sdist issue.
- Removed unsafe DOM-to-HTML and DOM-to-download flows, bounded note parsing,
kept backend exception details out of API payloads, and confined upload
writes to managed roots.
- Updated the embedded Earworm/Akousma package from 0.4.0 to 0.6.0 and aligned
the documented Listening Stack versions with the canonical public releases.

## 0.3.0 — Cosmoaudition, Matter Analysis, and audio reliability

Expand Down
4 changes: 2 additions & 2 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ authors:
- family-names: "Isaza"
given-names: "eme"
affiliation: "Sonic Field Labs"
version: "0.3.0"
date-released: "2026-07-31"
version: "0.3.1"
date-released: "2026-08-03"
license: "MPL-2.0"
repository-code: "https://github.com/sonicfieldlabs/germ"
abstract: "germ is a local generative microsound environment whose generated sounds retain prompts, parents, mutations, listening metadata, and Earworm-compatible lineage."
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ listened to, and traced through lineage. A listening from Oída can become a
prompt or source in GERM; a successful render can become a descendant in
Akousmata and return to Oída for another listening.

Current release: `0.3.0`.
Current release: `0.3.1`.

GERM is an independent Sonic Field Labs project. It can use Stable Audio 3
providers, but it is not an official Stability AI product.
Expand Down Expand Up @@ -147,12 +147,12 @@ listening, re-listening, sonic memory, and cultivation.

| Component | Version / contract | GERM integration |
| --- | --- | --- |
| [OÍDA](https://github.com/sonicfieldlabs/oida) | 0.6.0 / `oida/gateway/v0.2` | Re-listen to generated sound, derive editable prompts, and retain a listening only when requested. |
| [Earworm](https://github.com/sonicfieldlabs/earworm) | 0.4.0 / akousma spec v1.3 | Export generation context and preserve provenance, lineage, location/capture, and covenants. |
| [Akousmata](https://github.com/sonicfieldlabs/akousmata) | 0.4.0 | Import remembered sound, prompt, or lineage; write successful generations back as child akousmata. |
| [AKOÚŌ](https://github.com/sonicfieldlabs/akouo) | `akouo/v0.7` | Keeps listening claims, evidence permissions, apparatus, and covenants consistent across the stack. |
| [Algophony](https://github.com/sonicfieldlabs/algophony) | 0.5.0 | Can evaluate lineage-bearing generation batches without changing GERM's generation state. |
| [ORAM](https://github.com/sonicfieldlabs/oram) | 0.4.0 | Uses the local GERM-compatible generation surface for constrained sound summoning and transformation. |
| [OÍDA](https://github.com/sonicfieldlabs/oida) | 0.9.1 / `oida/gateway/v0.5` | Re-listen to generated sound, derive editable prompts, and retain a listening only when requested. |
| [Earworm](https://github.com/sonicfieldlabs/earworm) | 0.6.0 / akousma spec v1.5 | Export generation context and preserve provenance, lineage, location/capture, and covenants. |
| [Akousmata](https://github.com/sonicfieldlabs/akousmata) | 0.6.0 | Import remembered sound, prompt, or lineage; write successful generations back as child akousmata. |
| [AKOÚŌ](https://github.com/sonicfieldlabs/akouo) | 0.9.0 / `akouo/v0.9` | Keeps listening claims, evidence permissions, apparatus, temporal passes, and covenants consistent across the stack. |
| [Algophony](https://github.com/sonicfieldlabs/algophony) | 0.5.1 | Can evaluate lineage-bearing generation batches without changing GERM's generation state. |
| [ORAM](https://github.com/sonicfieldlabs/oram) | 0.4.1 | Uses the local GERM-compatible generation surface for constrained sound summoning and transformation. |

The core handoff is:

Expand Down
6 changes: 6 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ only through configured model roots. Treat every model artifact as executable
input: use the official Safetensors releases, verify provenance, and do not load
untrusted pickle-based checkpoints.

The default server binds to `127.0.0.1`, validates Host headers, and rejects
foreign browser origins for state-changing requests. Audio and metadata routes
normalize paths and require resolved files to remain inside their configured
input, output, metadata, model, upload, or scratch roots before filesystem
access. Upload writes are confined to managed upload or scratch directories.

## Temporary Upstream PyTorch Exceptions

Stable Audio 3 still pins PyTorch 2.7.1 upstream. GERM overrides that constraint
Expand Down
4 changes: 2 additions & 2 deletions apps/macos/script/build_and_run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ APP_NAME="germ"
EXECUTABLE_NAME="germ-macos"
BUNDLE_ID="org.sonicfield.germ"
MIN_SYSTEM_VERSION="13.0"
MARKETING_VERSION="0.3.0"
BUNDLE_VERSION="3"
MARKETING_VERSION="0.3.1"
BUNDLE_VERSION="4"

ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
# Keep the runnable bundle where repository users expect to find apps. The
Expand Down
60 changes: 47 additions & 13 deletions dashboard/static/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2275,6 +2275,20 @@ function outputUrl(path) {
return `${baseUrl()}/files/${safePath}`;
}

function downloadableOutputUrl(path) {
const candidate = outputUrl(path);
if (candidate.startsWith("blob:")) return candidate;
try {
const url = new URL(candidate, window.location.href);
const backendOrigin = new URL(baseUrl(), window.location.href).origin;
if (!new Set([window.location.origin, backendOrigin]).has(url.origin)) return "";
if (url.protocol !== "http:" && url.protocol !== "https:") return "";
return url.href;
} catch {
return "";
}
}

function wavetableExportUrl(id, format = "gwt") {
if (!id) return "#";
return `${baseUrl()}/wavetables/${encodeURIComponent(id)}/export?format=${encodeURIComponent(format)}`;
Expand Down Expand Up @@ -2336,8 +2350,8 @@ function metadataSummary(metadata) {
return pieces.filter(Boolean).join(" | ");
}

function trackChips(metadata) {
if (!metadata) return "<span>provider -</span><span>model -</span><span>seed -</span>";
function trackChipValues(metadata) {
if (!metadata) return ["provider -", "model -", "seed -"];
const mode = metadata.germinator_mode || modeAliases[metadata.mode] || metadata.mode;
return [
`provider ${metadata.provider || "-"}`,
Expand All @@ -2347,9 +2361,16 @@ function trackChips(metadata) {
`seed ${metadata.seed ?? "-"}`,
`cfg ${metadata.cfg_scale ?? "-"}`,
`steps ${metadata.steps ?? "-"}`,
]
.map((item) => `<span>${escapeHtml(item)}</span>`)
.join("");
];
}

function renderTrackChips(target, metadata) {
const chips = trackChipValues(metadata).map((value) => {
const chip = document.createElement("span");
chip.textContent = String(value);
return chip;
});
target.replaceChildren(...chips);
}

async function setCurrentTrack(audioPath, metadataPath, metadata = null) {
Expand All @@ -2372,7 +2393,7 @@ async function setCurrentTrack(audioPath, metadataPath, metadata = null) {
$("trackTitle").textContent = displayNameFromPath(audioPath);
$("audioPath").value = audioPath;
$("metadataPath").value = metadataPath || loadedMetadata?.metadata_path || "";
$("trackMeta").innerHTML = trackChips(loadedMetadata);
renderTrackChips($("trackMeta"), loadedMetadata);
$("audioPlayer").src = outputUrl(audioPath);
$("playPauseBtn").disabled = false;
$("playhead").disabled = false;
Expand Down Expand Up @@ -20776,15 +20797,28 @@ if ($("loopToggle")) {
}

if ($("downloadBtn")) {
$("downloadBtn").addEventListener("click", () => {
$("downloadBtn").addEventListener("click", async () => {
const path = $("audioPath")?.value;
if (!path) return;
const a = document.createElement("a");
a.href = outputUrl(path);
a.download = path.split("/").pop() || "download.wav";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
const href = downloadableOutputUrl(path);
if (!href) {
setState("Download Blocked", "warn", "The selected sound does not have a trusted download URL.");
return;
}
try {
const response = await fetch(href, { credentials: "same-origin" });
if (!response.ok) throw new Error(`Download failed (${response.status})`);
const objectUrl = URL.createObjectURL(await response.blob());
const a = document.createElement("a");
a.href = objectUrl;
a.download = safeOutputName(path.split("/").pop() || "download.wav");
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.setTimeout(() => URL.revokeObjectURL(objectUrl), 0);
} catch (error) {
setState("Download Failed", "bad", error.message);
}
});
}

Expand Down
8 changes: 4 additions & 4 deletions docs/oida-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,23 @@ The shared-store bridge, `/import` handler, structured editable prompt handoff,
record/lineage endpoints, prompt and sound handoffs, re-listening action, optional derived-memory
write, and the self-contained lineage explorer are **implemented and tested**.

## Current contract: spec v1.3 (Earworm v0.4, 2026-07-14)
## Current contract: spec v1.5 (Earworm v0.6)

The bridge consumes and writes the current Akousma spec v1.3 while retaining the
The bridge consumes and writes the current Akousma spec v1.5 while retaining the
v1.0/v1.1 read compatibility required by existing memories:

- **Skimmable summaries** — generation records carry `summary: "germ <operation>: <prompt>"`;
prompt derivation prefers the record's own summary, then reads both raw
(v1.0) and enveloped (v1.1 `{contract, created_at, summary, payload}`)
listening entries. germ's own entries are pinned to `germ/v0.1`; raw AKOÚŌ
output is pinned to the current `akouo/v0.7` contract. Existing envelopes and
output is pinned to the current `akouo/v0.9` contract. Existing envelopes and
foreign producer blocks are preserved rather than reshaped.
- **Kinship** — `POST /akousma/generation` accepts typed `relations`
(`variant_of`, `series_with`, …), and re-registering the same audio content
auto-links `same_source_as` to the previous holder. The lineage endpoint and
explorer expose relations in both directions without confusing them with
causal parents.
- **Sovereign listening** — generation registration accepts the optional v1.3
- **Sovereign listening** — generation registration accepts the optional v1.5
`covenant` identity/honest-absence block and validates it through py-akousma.
Sound imports carry that covenant context into germ source metadata, never
reconstruct withheld content, and deliberately do not duplicate the
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[project]
name = "germ"
version = "0.3.0"
version = "0.3.1"
description = "Local-first modular laboratory for generative microsound, Stable Audio workflows, and lineage-aware cultivation."
readme = "README.md"
license = "MPL-2.0"
requires-python = ">=3.10"
authors = [{ name = "Sonic Field Labs" }]
dependencies = [
"akousma>=0.4.0",
"akousma>=0.6.0",
"fastapi>=0.139",
"httpx>=0.27.0",
"pydantic>=2.7.0",
Expand All @@ -23,7 +23,7 @@ override-dependencies = [
]

[tool.uv.sources]
akousma = { git = "https://github.com/sonicfieldlabs/earworm.git", tag = "v0.4.0", subdirectory = "packages/py-akousma" }
akousma = { git = "https://github.com/sonicfieldlabs/earworm.git", tag = "v0.6.0", subdirectory = "packages/py-akousma" }

[project.urls]
Homepage = "https://github.com/sonicfieldlabs/germ"
Expand Down
4 changes: 2 additions & 2 deletions server/akousma_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def resolve_audio_path(store, record: dict[str, Any]) -> Path | None:
)

GERM_CONTRACT = "germ/v0.1"
AKOUO_CONTRACT = "akouo/v0.7"
AKOUO_CONTRACT = "akouo/v0.9"
PROMPT_HANDOFF_CONTRACT = "oida-germ.prompt/v0.1"


Expand Down Expand Up @@ -423,7 +423,7 @@ def record_generation(
store=None,
) -> dict[str, Any]:
"""Write a germ generation into the shared store as a new akousma
(spec v1.3).
(spec v1.5).

The audio stays where germ wrote it (referenced by ``file://`` uri +
content hash); ``lineage.parent_akousma_ids`` points at the source
Expand Down
17 changes: 8 additions & 9 deletions server/huggingface_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,18 @@ def auth_status() -> dict[str, Any]:
"detail": None,
}
if not result["available"]:
status["detail"] = result["stderr"]
status["detail"] = "Hugging Face CLI is unavailable."
return status

if result["returncode"] != 0:
detail = (result["stderr"] or result["stdout"]).strip()
status["detail"] = detail or "hf auth whoami failed."
status["detail"] = "Hugging Face CLI authentication is unavailable."
return status

try:
account = json.loads(result["stdout"] or "{}")
except (json.JSONDecodeError, RecursionError):
account = {"raw": result["stdout"].strip()}
status["detail"] = "Hugging Face CLI returned an unreadable account response."
return status

status["logged_in"] = True
status["account"] = account
Expand All @@ -99,11 +99,10 @@ def model_access_status(repo_id: str) -> dict[str, Any]:
base = {
"repo": repo_id,
"file": "model_config.json",
"command": result["command"],
"returncode": result["returncode"],
}
if not result["available"]:
return {**base, "status": "hf_missing", "detail": result["stderr"]}
return {**base, "status": "hf_missing", "detail": "Hugging Face CLI is unavailable."}

output = f"{result['stdout']}\n{result['stderr']}".strip()
lowered = output.lower()
Expand All @@ -113,11 +112,11 @@ def model_access_status(repo_id: str) -> dict[str, Any]:
return {
**base,
"status": "requires_approval_or_login",
"detail": output,
"detail": "Model access requires accepted terms and an authenticated read token.",
}
if "not logged in" in lowered or "401" in lowered or "unauthorized" in lowered:
return {**base, "status": "not_logged_in", "detail": output}
return {**base, "status": "error", "detail": output}
return {**base, "status": "not_logged_in", "detail": "Hugging Face authentication is required."}
return {**base, "status": "error", "detail": "Hugging Face model access check failed."}


def stable_audio_hf_status(*, check_models: bool = False) -> dict[str, Any]:
Expand Down
2 changes: 1 addition & 1 deletion server/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

PRODUCT_NAME = "germ"
PRODUCT_DESCRIPTION = "open-source modular lab for generative microsound"
__version__ = "0.3.0"
__version__ = "0.3.1"
LEGACY_ENGINE_NAME = "Germinator"
SOUND_MATTER_CONCEPT = "sound_matter"
SOUND_MATTER_SCALES = ["micro", "meso", "macro"]
5 changes: 4 additions & 1 deletion server/routes/cosmoaudition.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from __future__ import annotations

import json
import logging
from pathlib import Path
from typing import Any, Literal
from uuid import uuid4
Expand All @@ -26,6 +27,7 @@
router = APIRouter(prefix="/cosmoaudition", tags=["cosmoaudition"])
MAX_ARCHIVES = 12
MAX_ARCHIVE_BYTES = 1_000_000
LOGGER = logging.getLogger(__name__)


def _bridge() -> CosmoauditionBridge:
Expand All @@ -37,11 +39,12 @@ def _bridge() -> CosmoauditionBridge:


def _bridge_status_from_error(exc: Exception) -> dict[str, Any]:
LOGGER.warning("Cosmoaudition bridge unavailable: %s", exc)
return {
"available": False,
"contract": COSMOAUDITION_GERM_CONTRACT,
"baseUrl": settings.cosmoaudition_url,
"error": str(exc)[:2_000],
"error": "Cosmoaudition bridge unavailable",
}


Expand Down
17 changes: 13 additions & 4 deletions server/routes/lora.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
from __future__ import annotations

import logging

from fastapi import APIRouter

from server.registry import registry
from server.schemas import LoraLoadRequest, LoraStrengthRequest


router = APIRouter()
LOGGER = logging.getLogger(__name__)


@router.post("/lora/load")
def load_lora(request: LoraLoadRequest) -> dict:
try:
return registry.get(request.provider).load_lora(request.paths)
except Exception as exc:
return {"status": "error", "provider": request.provider, "error": str(exc)}
except Exception:
LOGGER.exception("LoRA load failed for provider %s", request.provider)
return {"status": "error", "provider": request.provider, "error": "LoRA load failed"}


@router.post("/lora/strength")
def set_lora_strength(request: LoraStrengthRequest) -> dict:
try:
provider = registry.get(request.provider)
return provider.set_lora_strength(request.strength, request.lora_index)
except Exception as exc:
return {"status": "error", "provider": request.provider, "error": str(exc)}
except Exception:
LOGGER.exception("LoRA strength update failed for provider %s", request.provider)
return {
"status": "error",
"provider": request.provider,
"error": "LoRA strength update failed",
}
Loading
Loading