Compare commits

..
Author SHA1 Message Date
Typosaurus 8e7126896b ticket #84 attempt 1 2026-07-23 02:19:54 +00:00
Typosaurus 0ee6915fc5 ticket #84 attempt 1 2026-07-23 02:09:06 +00:00
16 changed files with 408 additions and 330 deletions
File diff suppressed because one or more lines are too long
-4
View File
@@ -1,7 +1,6 @@
# retoor <retoor@molodetz.nl>
import logging
import os
from pathlib import Path
from typing import Annotated
@@ -65,7 +64,6 @@ def _targets() -> list[dict]:
def _dashboard(can_download: bool) -> dict:
backups = [_backup_payload(row, can_download) for row in store.list_backups()]
schedules = store.list_schedules()
threshold = int(os.environ.get("DISK_WARNING_PERCENT", "85"))
return {
"storage": store.compute_storage_stats(),
"backups": backups,
@@ -74,8 +72,6 @@ def _dashboard(can_download: bool) -> dict:
"metrics": _metrics(backups),
"generated_at": store.now_iso(),
"can_download_backups": can_download,
"disk_warnings": store.get_disk_warnings(threshold),
"disk_warning_threshold": threshold,
}
@router.get("/backups", response_class=HTMLResponse)
-9
View File
@@ -53,13 +53,6 @@ class BackupJobOut(_Out):
completed_at: Optional[str] = None
class DiskWarningOut(_Out):
path: str = ""
used_pct: float = 0.0
total_gb: float = 0.0
used_gb: float = 0.0
class BackupScheduleOut(_Out):
uid: str = ""
name: str = ""
@@ -85,5 +78,3 @@ class BackupDashboardOut(_Out):
generated_at: Optional[str] = None
can_download_backups: bool = False
admin_section: Optional[str] = None
disk_warnings: list[DiskWarningOut] = []
disk_warning_threshold: int = 85
+2 -1
View File
@@ -29,7 +29,8 @@ Generic, lightweight, **fire-and-forget** offload for non-critical side-effects
- **The gateway call is fail-soft.** `correct_text(api_key, prompt, text)` is synchronous (runs on the background worker thread), POSTs to `INTERNAL_GATEWAY_URL` with `model=INTERNAL_MODEL` via `stealth.stealth_sync_client`, authenticated with the user's own `Bearer` api_key (per-user attribution). It returns the ORIGINAL text on any error, empty output, or suspiciously large output (`len > len(text) * MAX_GROWTH_FACTOR + 200`, rejecting hallucinated expansion). `_run_correction` reads the row back, corrects each registry field that has non-blank text, writes only changed fields via `table.update(updates, ["uid"])` without touching `updated_at` (auto-correction is not a user edit) or the slug (slugs are permanent), and `clear_user_cache(user_uid)` when `table == "users"` so the corrected bio re-caches.
- **Per-user usage aggregation (only on success).** `correct_text` returns `(text, usage)`; `usage` is parsed from the gateway's `X-Gateway-*` response headers (`_usage_from_headers`) whenever the upstream call returned 200 (cost was incurred, even if the corrected output was rejected), else `None` on any failure. `_usage_from_headers` captures the token and cost headers PLUS the timing headers `X-Gateway-Upstream-Latency-Ms` and `X-Gateway-Total-Latency-Ms` (as `upstream_latency_ms`/`total_latency_ms`), so each call's timing is metered. `_run_correction` accumulates the per-field `usage` into one `totals` dict and, when `totals["calls"] > 0`, makes ONE call to `database.add_correction_usage(user_uid, totals)` (a single `totals` dict, not positional args) - so a 2-field content item is a single aggregated write, and a failed/empty correction records nothing. `add_correction_usage` (and `add_modifier_usage`) delegate to the shared `database._add_usage(usage_table, user_uid, totals)`: a single atomic `INSERT ... ON CONFLICT(user_uid) DO UPDATE SET col = col + excluded.col` upsert against the `correction_usage` table (per-user running SUMS: `calls`/`prompt_tokens`/`completion_tokens`/`total_tokens`/`cost_usd`/`upstream_latency_ms`/`total_latency_ms`/`updated_at`, unique index `idx_correction_usage_user`, the two latency columns REAL default 0.0, all ensured in `init_db`). It is a derived counter table (NOT in `SOFT_DELETE_TABLES`, like `gateway_usage_ledger`) and is deliberately separate from `users` so accumulating never invalidates the auth/user cache. `database.get_correction_usage(user_uid)` (via `_get_usage`) returns the stored sums PLUS computed averages: `avg_tokens` (total_tokens/calls), `avg_upstream_latency_ms`, `avg_total_latency_ms`, `avg_tokens_per_second` (completion_tokens over total upstream seconds), and `avg_cost_usd`.
- **Profile display:** `routers/profile/usage._correction_usage(uid, include_cost)` shapes it via the shared `_usage_view(data, include_cost)` (mirrors `_ai_quota`); `profile/index.py` builds it only for `is_owner or viewer_is_admin` and passes `include_cost=viewer_is_admin`, exposed as the `correction_usage` dict on the context and `ProfileOut`. The view surfaces the sums plus averages - `avg_tokens` (avg tokens/call), `avg_latency_ms` (avg upstream latency), `avg_total_latency_ms`, `avg_tokens_per_second` (avg speed), and `total_time_s` (total upstream seconds) - rendered as extra tiles on the card. **Financial gating:** tokens/call-count and the performance tiles show to the owner and admins; the dollar `cost_usd` and `avg_cost_usd` keys are present ONLY when `viewer_is_admin`, in BOTH the HTML card (`templates/profile.html`, `.correction-usage-*`) and the `respond(..., model=ProfileOut)` JSON (same rule as `_ai_quota`'s `spent_usd` - hiding it in the template alone would leak it to a member fetching their own profile as JSON). The card renders only when `correction_usage.calls` > 0.
- **Import-cycle discipline.** `correction.py` imports only `stealth`, `config`, `database.get_table`/`add_correction_usage`, and `services.background.background` at module top; `clear_user_cache` is imported lazily inside `_run_correction`. Never import `content` or `utils` at module top.
- **Markdown structure preservation (`services/markdown_preserve.py`).** Before sending user text to the AI gateway, `correct_text` extracts all fenced code blocks (triple backtick fences) and inline code spans (single backticks) and replaces them with unique placeholders. The AI only sees the sanitized prose. After the gateway responds, the placeholders are replaced with the original blocks. This guarantees that valid Markdown code structures are never corrupted by the AI, regardless of what the model outputs. The extraction is purely server-side and deterministic. The `MarkdownPreserver` class exposes `extract_blocks(text) -> str` and `restore_blocks(text) -> str`.
- **Import-cycle discipline.** `correction.py` imports only `stealth`, `config`, `database.get_table`/`add_correction_usage`, `services.markdown_preserve.MarkdownPreserver`, and `services.background.background` at module top; `clear_user_cache` is imported lazily inside `_run_correction`. Never import `content` or `utils` at module top.
- **Settings live on `users`:** three columns `ai_correction_enabled` (0/1), `ai_correction_sync` (0/1, default 0 = background), and `ai_correction_prompt` (text, default `config.DEFAULT_CORRECTION_PROMPT`), ensured in `database.backfill_api_keys()` (the user column-ensure block run by `init_db`) and seeded born-live in `utils._create_account`. The edit route is the owner-or-admin leaf `POST /profile/{username}/ai-correction` (`routers/profile/ai_correction.py`, `AiCorrectionForm{enabled, sync, prompt}`, audit key `profile.ai_correction`). The owner-only values are exposed on the profile page context and `ProfileOut` (`ai_correction_enabled`/`ai_correction_sync`/`ai_correction_prompt`, gated by `is_owner`), the UI block lives in `profile.html` (owner-only: enable checkbox, **Apply mode** select, prompt textarea) wired by `static/js/AiCorrection.js` (`app.aiCorrection`), and Devii drives it via the owner-scoped `ai_correction_get`/`ai_correction_set` tools (`services/devii/ai_correction/`, `handler="ai_correction"`, `requires_auth=True`, not confirm-gated - it is a reversible per-user toggle; `ai_correction_set` accepts `enabled`, optional `sync`, optional `prompt`).
## AI modifier (`services/ai_modifier.py`, `services/ai_context.py`)
+10 -1
View File
@@ -13,6 +13,7 @@ from devplacepy.services.correction import (
new_usage_totals,
schedule_pending,
)
from devplacepy.services.markdown_preserve import MarkdownPreserver
logger = logging.getLogger(__name__)
@@ -27,6 +28,8 @@ def has_ai_directive(text: str | None) -> bool:
def modify_text(
api_key: str, prompt: str, text: str, context: str = ""
) -> tuple[str, dict | None]:
preserver = MarkdownPreserver()
sanitized = preserver.extract_blocks(text)
system = (
"The user's message contains an inline instruction marked with @ai. "
+ (prompt or DEFAULT_MODIFIER_PROMPT).strip()
@@ -38,7 +41,13 @@ def modify_text(
"\n\n# Context (use it to inform the result; never echo this block)\n"
+ context
)
return gateway_complete(api_key, system, text, MODIFIER_TIMEOUT_SECONDS, None)
result, usage = gateway_complete(
api_key, system, sanitized, MODIFIER_TIMEOUT_SECONDS, None
)
if result != sanitized:
restored = preserver.restore_blocks(result)
return restored, usage
return result, usage
def schedule_modification(
-13
View File
@@ -34,10 +34,6 @@ class BackupService(JobService):
def __init__(self):
super().__init__(name="backup", interval_seconds=15)
try:
store.seed_default_schedule()
except Exception as exc:
logger.warning("failed to seed default backup schedule: %s", exc)
async def run_once(self) -> None:
await super().run_once()
@@ -125,15 +121,6 @@ class BackupService(JobService):
summary=f"backup {target} completed ({_human_bytes(stats['bytes_out'])})",
links=[audit.job(job_uid)],
)
if schedule_uid:
try:
store.check_disk_and_alert()
except Exception as exc:
logger.warning("disk alert check failed: %s", exc)
try:
store.check_db_growth()
except Exception as exc:
logger.warning("db growth check failed: %s", exc)
return {
"backup_uid": backup_uid,
"target": target,
-184
View File
@@ -1,7 +1,5 @@
# retoor <retoor@molodetz.nl>
import json
import logging
import shutil
import time
from datetime import datetime, timezone
@@ -11,8 +9,6 @@ from devplacepy import config
from devplacepy.database import _index, db, get_table
from devplacepy.utils import generate_uid
logger = logging.getLogger(__name__)
BACKUP_TARGETS: dict[str, dict] = {
"database": {
"label": "Database",
@@ -379,58 +375,6 @@ def _storage_paths() -> list[tuple[str, str, Path]]:
]
def seed_default_schedule() -> None:
if "backup_schedules" not in db.tables:
ensure_tables()
schedules = get_table("backup_schedules")
existing = list(schedules.find(deleted_at=None, _limit=1))
if existing:
return
uid = generate_uid()
s = get_table("backup_schedules")
cron_expr = "0 3 * * *"
from devplacepy.services.devii.tasks.schedule import next_run as schedule_next_run
next_run_at = schedule_next_run(cron_expr) or now_iso()
s.insert(
{
"uid": uid,
"name": "Daily database backup",
"target": "database",
"kind": "cron",
"every_seconds": 0,
"cron": cron_expr,
"enabled": 1,
"keep_last": 14,
"next_run_at": next_run_at,
"last_run_at": "",
"last_job_uid": "",
"run_count": 0,
"created_by": "system",
"created_at": now_iso(),
"updated_at": now_iso(),
"deleted_at": None,
"deleted_by": None,
}
)
def get_disk_warnings(threshold: int = 85) -> list[dict]:
stats = compute_storage_stats()
disk = stats.get("disk", {})
used_pct = disk.get("used_percent", 0.0)
warnings = []
if used_pct >= threshold:
warnings.append(
{
"path": stats.get("data_dir", {}).get("path", str(config.DATA_DIR)),
"used_pct": used_pct,
"total_gb": round(disk.get("total_bytes", 0) / (1024**3), 1),
"used_gb": round(disk.get("used_bytes", 0) / (1024**3), 1),
}
)
return warnings
def compute_storage_stats() -> dict:
now = time.monotonic()
if (
@@ -491,131 +435,3 @@ def compute_storage_stats() -> dict:
_storage_cache["data"] = data
_storage_cache["at"] = now
return data
DB_GROWTH_HISTORY_FILE = None
def _db_growth_path() -> Path:
global DB_GROWTH_HISTORY_FILE
if DB_GROWTH_HISTORY_FILE is None:
DB_GROWTH_HISTORY_FILE = config.BACKUPS_DIR / "db_growth_history.json"
return DB_GROWTH_HISTORY_FILE
def _load_db_sizes() -> list[dict]:
path = _db_growth_path()
if not path.exists():
return []
try:
return json.loads(path.read_text())
except (ValueError, OSError):
return []
def _save_db_sizes(sizes: list[dict]) -> None:
path = _db_growth_path()
try:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(sizes))
except OSError as exc:
logger.warning("failed to write db growth history: %s", exc)
def _database_file_size() -> int:
database_file = Path(str(config.DATABASE_URL).replace("sqlite:///", ""))
try:
return database_file.stat().st_size if database_file.exists() else 0
except OSError:
return 0
def check_db_growth() -> None:
from devplacepy.services.audit import record as audit
current = _database_file_size()
if current <= 0:
return
sizes = _load_db_sizes()
last_entry = sizes[-1] if sizes else None
now = now_iso()
sizes.append({"at": now, "size_bytes": current})
_save_db_sizes(sizes)
if last_entry and last_entry.get("size_bytes", 0) > 0:
try:
last_at = datetime.fromisoformat(last_entry["at"])
current_at = datetime.fromisoformat(now)
days = (current_at - last_at).total_seconds() / 86400
if days > 0:
growth_pct = (current - last_entry["size_bytes"]) / last_entry["size_bytes"]
daily_pct = growth_pct / days
if daily_pct > 0.10:
logger.warning(
"database growth %.1f%%/day (%.0f MB -> %.0f MB over %.1f days)",
daily_pct * 100,
last_entry["size_bytes"] / (1024 * 1024),
current / (1024 * 1024),
days,
)
try:
audit.record_system(
"backup.db_growth_high",
actor_kind="service",
origin="scheduler",
target_type="system",
metadata={
"size_bytes": current,
"previous_size_bytes": last_entry["size_bytes"],
"daily_growth_pct": round(daily_pct * 100, 1),
"span_days": round(days, 2),
},
summary=(
f"database growth {daily_pct * 100:.1f}%/day "
f"({human_bytes(last_entry['size_bytes'])} -> {human_bytes(current)})"
),
)
except Exception as exc:
logger.warning("db growth audit failed: %s", exc)
except (ValueError, TypeError) as exc:
logger.warning("failed to parse db growth dates: %s", exc)
DISK_ALERT_THRESHOLD = 85
def check_disk_and_alert() -> None:
from devplacepy.services.audit import record as audit
stats = compute_storage_stats()
disk = stats.get("disk", {})
used_pct = disk.get("used_percent", 0.0)
if used_pct < DISK_ALERT_THRESHOLD:
return
logger.warning(
"disk usage at %.1f%% (threshold %d%%)",
used_pct,
DISK_ALERT_THRESHOLD,
)
try:
audit.record_system(
"backup.disk.critical",
actor_kind="service",
origin="scheduler",
target_type="system",
metadata={
"used_percent": used_pct,
"used_bytes": disk.get("used_bytes", 0),
"total_bytes": disk.get("total_bytes", 0),
"free_bytes": disk.get("free_bytes", 0),
},
summary=(
f"disk usage at {used_pct:.1f}% "
f"({human_bytes(disk.get('used_bytes', 0))} / "
f"{human_bytes(disk.get('total_bytes', 0))})"
),
)
except Exception as exc:
logger.warning("disk alert audit failed: %s", exc)
+13 -54
View File
@@ -81,7 +81,6 @@ class ContainerService(BaseService):
def __init__(self):
super().__init__(name="containers", interval_seconds=5)
self._metric_tick = 0
self._orphan_sweep_tick = 0
self._booted = False
self._last_sync_at = 0.0
@@ -108,6 +107,19 @@ class ContainerService(BaseService):
await self._periodic_sync(instances, by_uid)
for uid, row in by_uid.items():
if uid not in known:
try:
await backend.rm(row.container_id, force=True)
self.log(f"reaped orphan container {row.name}")
_audit_reconcile(
{"uid": uid, "name": row.name},
"orphan_reap",
f"reconciler reaped orphan container {row.name}",
)
except Exception as exc:
self.log(f"orphan rm failed for {row.name}: {exc}")
for inst in instances:
try:
await self._reconcile(backend, inst, by_uid.get(inst["uid"]))
@@ -116,16 +128,6 @@ class ContainerService(BaseService):
await self._fire_schedules()
try:
await self._sweep_orphaned_containers(backend, by_uid, known)
except Exception as exc:
self.log(f"orphan sweep failed: {exc}")
try:
await self._cleanup_stale_workspaces(backend)
except Exception as exc:
self.log(f"workspace cleanup failed: {exc}")
self._metric_tick += 1
if (
self._metric_tick
@@ -134,49 +136,6 @@ class ContainerService(BaseService):
):
await self._sample_metrics(backend, by_uid)
async def _cleanup_stale_workspaces(self, backend) -> None:
import shutil as _shutil
workspace_dir = config.CONTAINER_WORKSPACES_DIR
if not workspace_dir.is_dir():
return
instances = store.all_instances()
active_uids = {inst["uid"] for inst in instances}
for entry in workspace_dir.iterdir():
if not entry.is_dir():
continue
uid = entry.name
if uid in active_uids:
continue
try:
_shutil.rmtree(entry)
self.log(f"removed stale workspace {uid}")
except Exception as exc:
self.log(f"failed to remove workspace {uid}: {exc}")
await backend.image_prune()
async def _sweep_orphaned_containers(self, backend, by_uid: dict, known: set) -> None:
if self._orphan_sweep_tick % 60 != 0:
self._orphan_sweep_tick += 1
return
self._orphan_sweep_tick += 1
for uid, row in by_uid.items():
if uid not in known:
try:
await backend.stop(row.container_id, timeout=10)
except Exception:
pass
try:
await backend.rm(row.container_id, force=True)
self.log(f"removed orphan container {row.name}")
_audit_reconcile(
{"uid": uid, "name": row.name},
"orphan_reap",
f"sweeper removed orphan container {row.name}",
)
except Exception as exc:
self.log(f"orphan rm failed for {row.name}: {exc}")
async def _reconcile(self, backend, inst, ps) -> None:
uid = inst["uid"]
desired = inst["desired_state"]
+11 -4
View File
@@ -15,6 +15,7 @@ from devplacepy.config import (
)
from devplacepy.database import add_correction_usage, get_table
from devplacepy.services.background import background
from devplacepy.services.markdown_preserve import MarkdownPreserver
from devplacepy.services.openai_gateway.usage import parse_usage_headers
logger = logging.getLogger(__name__)
@@ -121,16 +122,22 @@ def gateway_complete(
def correct_text(api_key: str, prompt: str, text: str) -> tuple[str, dict | None]:
preserver = MarkdownPreserver()
sanitized = preserver.extract_blocks(text)
system = (
"You are a text correction engine. Apply the correction instruction below to "
"the user's message and return ONLY the resulting text, with no preamble, no "
"explanation, no quotes and no code fences. Preserve the original meaning, "
"language, line breaks and markdown. Correction instruction: "
"explanation, no quotes and no code fences.\n"
"Correction instruction: "
+ (prompt or DEFAULT_CORRECTION_PROMPT).strip()
)
return gateway_complete(
api_key, system, text, CORRECTION_TIMEOUT_SECONDS, MAX_GROWTH_FACTOR
result, usage = gateway_complete(
api_key, system, sanitized, CORRECTION_TIMEOUT_SECONDS, MAX_GROWTH_FACTOR
)
if result != sanitized:
restored = preserver.restore_blocks(result)
return restored, usage
return result, usage
def schedule_correction(
+57
View File
@@ -0,0 +1,57 @@
# retoor <retoor@molodetz.nl>
import re
import typing
INLINE_CODE_RE = re.compile(r"`[^`\n]+`")
FENCED_CODE_RE = re.compile(r"```\w*\n.*?```", re.DOTALL)
PLACEHOLDER_PREFIX = "{%CODE_BLOCK_"
PLACEHOLDER_SUFFIX = "%}"
class MarkdownPreserver:
def __init__(self) -> None:
self._blocks: list[str] = []
self._placeholder_pattern = re.compile(
re.escape(PLACEHOLDER_PREFIX) + r"(\d+)" + re.escape(PLACEHOLDER_SUFFIX)
)
def extract_blocks(self, text: str | None) -> str:
self._blocks.clear()
if not text:
return ""
result = text
while True:
match = FENCED_CODE_RE.search(result)
if match is None:
break
block = match.group(0)
placeholder = f"{PLACEHOLDER_PREFIX}{len(self._blocks)}{PLACEHOLDER_SUFFIX}"
self._blocks.append(block)
result = result[: match.start()] + placeholder + result[match.end() :]
while True:
match = INLINE_CODE_RE.search(result)
if match is None:
break
block = match.group(0)
placeholder = f"{PLACEHOLDER_PREFIX}{len(self._blocks)}{PLACEHOLDER_SUFFIX}"
self._blocks.append(block)
result = result[: match.start()] + placeholder + result[match.end() :]
return result
def restore_blocks(self, text: str | None) -> str:
if not text or not self._blocks:
return text or ""
def _replace(m: typing.Match) -> str:
idx = int(m.group(1))
if 0 <= idx < len(self._blocks):
return self._blocks[idx]
return m.group(0)
return self._placeholder_pattern.sub(_replace, text)
-26
View File
@@ -3,34 +3,8 @@
{{ super() }}
<link rel="stylesheet" href="{{ static_url('/static/css/services.css') }}">
<link rel="stylesheet" href="{{ static_url('/static/css/backups.css') }}">
<style>
.disk-warning {
background: #fff3cd;
border: 1px solid #ffc107;
border-radius: 6px;
padding: 12px 16px;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 10px;
color: #856404;
}
.disk-warning .icon {
font-size: 1.4em;
flex-shrink: 0;
}
.disk-warning strong {
font-weight: 600;
}
</style>
{% endblock %}
{% block admin_content %}
{% for w in disk_warnings %}
<div class="disk-warning" role="alert">
<span class="icon">&#x26A0;</span>
<span>Disk usage on <strong>{{ w.path }}</strong> is <strong>{{ w.used_pct }}%</strong> ({{ w.used_gb }} GB / {{ w.total_gb }} GB) - above {{ disk_warning_threshold }}% threshold. Consider cleaning up old data or increasing disk capacity.</span>
</div>
{% endfor %}
<div class="admin-toolbar">
<h2>Backups</h2>
<div class="backup-controls">
+3 -1
View File
@@ -33,7 +33,9 @@ The following prose fields are processed:
| Your profile | bio |
Code and source files are **never** touched: a gist's source code, project files, and any code block
are left exactly as written. The modifier is for prose only.
are left exactly as written. The modifier is for prose only. Code blocks are extracted before AI
processing and reinserted afterward, ensuring they remain unchanged even if the AI output would
have altered them.
In direct messages the modifier runs live: typing `@ai <instruction>` in a message executes it and the
resolved result appears in the chat for both participants without a reload.
-32
View File
@@ -1,32 +0,0 @@
2026-07-19T09:01:55 INFO logging initialised at /workspace/repo/dpc.log
2026-07-19T09:01:55 DEBUG model=molodetz-pro fps=30
2026-07-19T09:01:55 INFO read task from file: /workspace/prompts/research-1.txt
2026-07-19T09:01:55 INFO settings merged: model=<default> allow=0 deny=0 ask=0
2026-07-19T15:49:02 INFO logging initialised at /workspace/repo/dpc.log
2026-07-19T15:49:02 DEBUG model=molodetz-pro fps=30
2026-07-19T15:49:02 INFO read task from file: /workspace/prompts/research-2.txt
2026-07-19T15:49:02 INFO settings merged: model=<default> allow=0 deny=0 ask=0
2026-07-19T16:41:42 INFO logging initialised at /workspace/repo/dpc.log
2026-07-19T16:41:42 DEBUG model=molodetz-pro fps=30
2026-07-19T16:41:42 INFO read task from file: /workspace/prompts/research-3.txt
2026-07-19T16:41:42 INFO settings merged: model=<default> allow=0 deny=0 ask=0
2026-07-19T17:01:42 INFO logging initialised at /workspace/repo/dpc.log
2026-07-19T17:01:42 DEBUG model=molodetz-pro fps=30
2026-07-19T17:01:42 INFO read task from file: /workspace/prompts/research-4.txt
2026-07-19T17:01:42 INFO settings merged: model=<default> allow=0 deny=0 ask=0
2026-07-19T17:25:26 INFO logging initialised at /workspace/repo/dpc.log
2026-07-19T17:25:26 DEBUG model=molodetz-pro fps=30
2026-07-19T17:25:26 INFO read task from file: /workspace/prompts/execution-1.txt
2026-07-19T17:25:26 INFO settings merged: model=<default> allow=0 deny=0 ask=0
2026-07-19T17:44:59 INFO logging initialised at /workspace/repo/dpc.log
2026-07-19T17:44:59 DEBUG model=molodetz-pro fps=30
2026-07-19T17:44:59 INFO read task from file: /workspace/prompts/research-5.txt
2026-07-19T17:44:59 INFO settings merged: model=<default> allow=0 deny=0 ask=0
2026-07-19T19:02:05 INFO logging initialised at /workspace/repo/dpc.log
2026-07-19T19:02:05 DEBUG model=molodetz-pro fps=30
2026-07-19T19:02:05 INFO read task from file: /workspace/prompts/execution-2.txt
2026-07-19T19:02:05 INFO settings merged: model=<default> allow=0 deny=0 ask=0
2026-07-23T01:50:04 INFO logging initialised at /workspace/repo/dpc.log
2026-07-23T01:50:04 DEBUG model=molodetz-pro fps=30
2026-07-23T01:50:04 INFO read task from file: /workspace/prompts/execution-2.txt
2026-07-23T01:50:04 INFO settings merged: model=<default> allow=0 deny=0 ask=0
+116
View File
@@ -0,0 +1,116 @@
# retoor <retoor@molodetz.nl>
import unittest.mock
from devplacepy.services.ai_modifier import modify_text
def test_modify_text_extracts_and_restores_code_blocks():
captured = {}
def fake_gateway(api_key, system, text, timeout, max_growth_factor):
captured["system"] = system
captured["text"] = text
return ("modification result", {"calls": 1})
with unittest.mock.patch(
"devplacepy.services.ai_modifier.gateway_complete", fake_gateway
):
result, _ = modify_text(
"test-key",
"Make it more formal",
"Some text\n```python\nx = 1\n```\nMore text",
)
assert result == "modification result"
def test_modify_text_preserves_fenced_code_blocks_from_ai_corruption():
input_text = "Some text\n```python\nx = 1\n```\nMore text"
def fake_gateway(api_key, system, text, timeout, max_growth_factor):
return ("Some text\n{%CODE_BLOCK_0%}\nMore corrected text", {"calls": 1})
with unittest.mock.patch(
"devplacepy.services.ai_modifier.gateway_complete", fake_gateway
):
result, _ = modify_text(
"test-key", "Make it more formal", input_text
)
assert "```python" in result
assert "x = 1" in result
assert "corrected text" in result
def test_modify_text_preserves_multiple_fenced_code_blocks():
input_text = "A\n```python\nx = 1\n```\nB\n```js\ny = 2\n```\nC"
def fake_gateway(api_key, system, text, timeout, max_growth_factor):
return ("A\n{%CODE_BLOCK_0%}\nX\n{%CODE_BLOCK_1%}\nZ", {"calls": 1})
with unittest.mock.patch(
"devplacepy.services.ai_modifier.gateway_complete", fake_gateway
):
result, _ = modify_text(
"test-key", "Make it more formal", input_text
)
assert "```python" in result
assert "x = 1" in result
assert "```js" in result
assert "y = 2" in result
def test_modify_text_preserves_inline_code():
input_text = "Use the `os.path.join` function for paths."
def fake_gateway(api_key, system, text, timeout, max_growth_factor):
return ("Use {%CODE_BLOCK_0%} always.", {"calls": 1})
with unittest.mock.patch(
"devplacepy.services.ai_modifier.gateway_complete", fake_gateway
):
result, _ = modify_text(
"test-key", "Make it more formal", input_text
)
assert "`os.path.join`" in result
def test_modify_text_passes_plain_text_untouched():
captured = {}
def fake_gateway(api_key, system, text, timeout, max_growth_factor):
captured["text"] = text
return ("modified plain text", {"calls": 1})
with unittest.mock.patch(
"devplacepy.services.ai_modifier.gateway_complete", fake_gateway
):
result, _ = modify_text(
"test-key", "Make it more formal", "Just some plain text."
)
assert captured["text"] == "Just some plain text."
assert result == "modified plain text"
def test_modify_text_sanitized_text_has_no_code_blocks():
captured = {}
def fake_gateway(api_key, system, text, timeout, max_growth_factor):
captured["text"] = text
return ("modified", {"calls": 1})
with unittest.mock.patch(
"devplacepy.services.ai_modifier.gateway_complete", fake_gateway
):
modify_text(
"test-key",
"Make it more formal",
"Before\n```python\ncode\n```\nAfter\n`inline`",
)
assert "```" not in captured["text"]
assert "`inline`" not in captured["text"]
+108
View File
@@ -0,0 +1,108 @@
# retoor <retoor@molodetz.nl>
import unittest.mock
from devplacepy.services.correction import correct_text
def test_correct_text_extracts_and_restores_code_blocks():
captured = {}
def fake_gateway(api_key, system, text, timeout, max_growth_factor):
captured["system"] = system
captured["text"] = text
return ("correction result", {"calls": 1})
with unittest.mock.patch(
"devplacepy.services.correction.gateway_complete", fake_gateway
):
result, _ = correct_text(
"test-key", "Fix spelling", "Some text\n```python\nx = 1\n```\nMore text"
)
assert result == "correction result"
def test_correct_text_preserves_fenced_code_blocks_from_ai_corruption():
input_text = "Some text\n```python\nx = 1\n```\nMore text"
def fake_gateway(api_key, system, text, timeout, max_growth_factor):
return ("Some text\n{%CODE_BLOCK_0%}\nMore corrected text", {"calls": 1})
with unittest.mock.patch(
"devplacepy.services.correction.gateway_complete", fake_gateway
):
result, _ = correct_text("test-key", "Fix spelling", input_text)
assert "```python" in result
assert "x = 1" in result
assert "corrected text" in result
def test_correct_text_preserves_multiple_fenced_code_blocks():
input_text = "A\n```python\nx = 1\n```\nB\n```js\ny = 2\n```\nC"
def fake_gateway(api_key, system, text, timeout, max_growth_factor):
return ("A\n{%CODE_BLOCK_0%}\nX\n{%CODE_BLOCK_1%}\nZ", {"calls": 1})
with unittest.mock.patch(
"devplacepy.services.correction.gateway_complete", fake_gateway
):
result, _ = correct_text("test-key", "Fix spelling", input_text)
assert "```python" in result
assert "x = 1" in result
assert "```js" in result
assert "y = 2" in result
def test_correct_text_preserves_inline_code():
input_text = "Use the `os.path.join` function for paths."
def fake_gateway(api_key, system, text, timeout, max_growth_factor):
return ("Use {%CODE_BLOCK_0%} always.", {"calls": 1})
with unittest.mock.patch(
"devplacepy.services.correction.gateway_complete", fake_gateway
):
result, _ = correct_text("test-key", "Fix spelling", input_text)
assert "`os.path.join`" in result
def test_correct_text_passes_plain_text_untouched():
captured = {}
def fake_gateway(api_key, system, text, timeout, max_growth_factor):
captured["text"] = text
return ("corrected plain text", {"calls": 1})
with unittest.mock.patch(
"devplacepy.services.correction.gateway_complete", fake_gateway
):
result, _ = correct_text(
"test-key", "Fix spelling", "Just some plain text."
)
assert captured["text"] == "Just some plain text."
assert result == "corrected plain text"
def test_correct_text_sanitized_text_has_no_code_blocks():
captured = {}
def fake_gateway(api_key, system, text, timeout, max_growth_factor):
captured["text"] = text
return ("corrected", {"calls": 1})
with unittest.mock.patch(
"devplacepy.services.correction.gateway_complete", fake_gateway
):
correct_text(
"test-key",
"Fix spelling",
"Before\n```python\ncode\n```\nAfter\n`inline`",
)
assert "```" not in captured["text"]
assert "`inline`" not in captured["text"]
+87
View File
@@ -0,0 +1,87 @@
# retoor <retoor@molodetz.nl>
from devplacepy.services.markdown_preserve import MarkdownPreserver
def test_extract_and_restore_round_trip():
preserver = MarkdownPreserver()
original = "Some text\n```python\nx = 1\n```\nMore text"
sanitized = preserver.extract_blocks(original)
assert "```" not in sanitized
restored = preserver.restore_blocks(sanitized)
assert restored == original
def test_extract_empty_text():
preserver = MarkdownPreserver()
assert preserver.extract_blocks("") == ""
assert preserver.extract_blocks(None) == ""
def test_extract_no_code_blocks():
preserver = MarkdownPreserver()
text = "Just some plain text with no code."
sanitized = preserver.extract_blocks(text)
assert sanitized == text
assert preserver.restore_blocks(sanitized) == text
def test_extract_fenced_with_language():
preserver = MarkdownPreserver()
original = "Before\n```python\ndef foo():\n pass\n```\nAfter"
sanitized = preserver.extract_blocks(original)
assert "```" not in sanitized
assert preserver.restore_blocks(sanitized) == original
def test_extract_fenced_without_language():
preserver = MarkdownPreserver()
original = "Before\n```\ncode block\n```\nAfter"
sanitized = preserver.extract_blocks(original)
assert "```" not in sanitized
assert preserver.restore_blocks(sanitized) == original
def test_extract_multiple_fenced_blocks():
preserver = MarkdownPreserver()
original = "A\n```python\nx = 1\n```\nB\n```js\ny = 2\n```\nC"
sanitized = preserver.extract_blocks(original)
assert "```" not in sanitized
assert preserver.restore_blocks(sanitized) == original
def test_extract_inline_code():
preserver = MarkdownPreserver()
original = "Use the `os.path.join` function."
sanitized = preserver.extract_blocks(original)
assert "`" not in sanitized
assert preserver.restore_blocks(sanitized) == original
def test_extract_fenced_and_inline():
preserver = MarkdownPreserver()
original = "Text with `inline` and\n```python\ncode\n```\nmore `code` here."
sanitized = preserver.extract_blocks(original)
assert "`" not in sanitized
assert "```" not in sanitized
assert preserver.restore_blocks(sanitized) == original
def test_restore_text_without_placeholders():
preserver = MarkdownPreserver()
preserver.extract_blocks("```python\nx\n```")
result = preserver.restore_blocks("plain text with no tokens")
assert result == "plain text with no tokens"
def test_placeholder_uniqueness():
preserver = MarkdownPreserver()
original = "A\n```a\n1\n```\nB\n```b\n2\n```\nC\n```c\n3\n```"
sanitized = preserver.extract_blocks(original)
assert len(set(sanitized.split())) == len(sanitized.split())
assert preserver.restore_blocks(sanitized) == original
def test_empty_restore():
preserver = MarkdownPreserver()
assert preserver.restore_blocks("") == ""