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
31 changes: 31 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
version: 2
updates:
- package-ecosystem: uv
directory: /
schedule:
interval: cron
cronjob: 37 4 * * *
timezone: Europe/Warsaw
open-pull-requests-limit: 1
versioning-strategy: increase-if-necessary
allow:
- dependency-name: costs
dependency-type: all
- dependency-name: goal
dependency-type: all
- dependency-name: pfix
dependency-type: all
- dependency-name: clickmd
dependency-type: all
- dependency-name: code2llm
dependency-type: all
groups:
internal-packages:
patterns:
- costs
- goal
- pfix
- clickmd
- code2llm
commit-message:
prefix: "chore(deps)"
35 changes: 35 additions & 0 deletions .github/internal-dependencies.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"schema": "goal.internal-dependencies/v1",
"packages": [
{
"name": "costs",
"repository": "semcod/costs",
"registry": "pypi",
"versioning": "semver"
},
{
"name": "goal",
"repository": "semcod/goal",
"registry": "pypi",
"versioning": "semver"
},
{
"name": "pfix",
"repository": "semcod/pfix",
"registry": "pypi",
"versioning": "semver"
},
{
"name": "clickmd",
"repository": "semcod/clickmd",
"registry": "pypi",
"versioning": "semver"
},
{
"name": "code2llm",
"repository": "semcod/code2llm",
"registry": "pypi",
"versioning": "semver"
}
]
}
36 changes: 36 additions & 0 deletions .github/workflows/internal-dependency-freshness.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Internal dependency freshness

on:
schedule:
- cron: "47 5 * * *"
workflow_dispatch:
pull_request:
paths:
- pyproject.toml
- uv.lock
- .github/internal-dependencies.json
- .github/workflows/internal-dependency-freshness.yml

permissions:
contents: read

jobs:
freshness:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.12"
- name: Install released checker
run: python -m pip install "goal==2.2.0"
- name: Verify published stable targets
run: goal dependencies --catalog .github/internal-dependencies.json --check > dependency-freshness.json
- name: Retain freshness evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: dependency-freshness
path: dependency-freshness.json
if-no-files-found: ignore
30 changes: 30 additions & 0 deletions .github/workflows/test-locked.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Locked tests

on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.13"]
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
- run: python -m pip install uv
- name: Install locked test dependencies
run: uv sync --locked --extra dev --python "${{ matrix.python-version }}"
- name: Test supported Python versions
run: uv run --no-sync python -m pytest -q
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1234,3 +1234,7 @@ _Last updated by [taskill](https://github.com/oqlos/taskill) at 2026-04-25 18:22
> Implemented a comprehensive configuration management system and CLI interface for the planfile project, including new command groups for backlog, sync, ticket, and validate operations.

<!-- taskill:status:end -->

## Dependency maintenance

See [internal dependency updates](docs/dependencies.md) for daily updates, locked tests and Python support.
29 changes: 29 additions & 0 deletions docs/dependencies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Internal dependency updates

This repository owns its dependency configuration and this guide, following the repository ownership principle in [wellmanifest/docs](https://github.com/wellmanifest/docs/blob/f64de5806577769672ebc1730d2e144b4c7671ec/README.md). The checker belongs to [semcod/goal](https://github.com/semcod/goal/blob/84f18540d14c24cc8ff5b7f202d2874344779ecc/docs/internal-dependencies.md).

## Python and installation

Application support remains Python >=3.10. Source inspection found no Goal imports or executable invocation in application Python code. Goal's old declarations moved to an `automation` dependency group requiring Python >=3.12, so updating release tooling does not remove Python 3.10 application support.

```sh
uv sync --locked --extra dev --python 3.10
uv run --no-sync python -m pytest -q
UV_PROJECT_ENVIRONMENT=.venv-automation uv sync --locked --group automation --python 3.12
```

The automation command uses a separate environment. Synchronize the application environment again after merging tested dependency updates.

## Daily checks

[Dependabot](../.github/dependabot.yml) checks the explicit costs, Goal, pfix, clickmd and code2llm catalog daily, including weekends, and groups updates into one PR. It includes transitive packages and may widen constraints when necessary. This is a bounded catalog, not discovery of every internal package. Local/Git sources and other packages need separate review.

[Freshness CI](../.github/workflows/internal-dependency-freshness.yml) uses released Goal 2.2.0 to compare uv.lock with the highest published stable three-part versions of the catalog packages actually used here. It has read-only repository permissions, runs daily/manually/on dependency PRs, and retains JSON evidence. Registry and resolution errors remain visible. An audit does not prove a deployed environment has installed the new versions.

[Locked tests](../.github/workflows/test-locked.yml) install the dev extra from uv.lock and run the full project suite on Python 3.10 and 3.13. A permissive version declaration alone does not refresh the lockfile; update creation, testing, merge and installation are separate steps.

## Delivery

The registry targets observed on 2026-09-05 are costs 0.2.0, Goal 2.2.0, pfix 0.1.79, clickmd 1.1.15 and code2llm 0.5.176. PR and Actions checks hold the test results; the [ecosystem rollout history](https://github.com/semcod/costs/tree/main/docs/dependencies) belongs to costs. Reports and instructions are versioned in repository docs, rather than temporary machine directories.

The Python 3.10 test run exposed imports of `datetime.UTC`, which was added after that version. Application and test code use `timezone.utc` while preserving UTC timestamps and date-based archive behavior. Project homepage/repository metadata now points to semcod/planfile. The observed PyPI release is 0.1.123 while the source project reports 0.1.125; merging this PR does not publish a new distribution or update PyPI metadata.
4 changes: 2 additions & 2 deletions planfile/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
__author__ = "Tom Sapletta"
__email__ = "tom@sapletta.com"

from datetime import UTC, datetime, timedelta
from datetime import timezone, datetime, timedelta
from pathlib import Path
from typing import TYPE_CHECKING

Expand Down Expand Up @@ -82,7 +82,7 @@ class Planfile:

@staticmethod
def _utcnow() -> datetime:
return datetime.now(UTC)
return datetime.now(timezone.utc)

def __init__(self, project_path: str = "."):
self.store = PlanfileStore(project_path)
Expand Down
24 changes: 12 additions & 12 deletions planfile/api/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import time
from collections import deque
from contextlib import asynccontextmanager, contextmanager
from datetime import UTC, datetime
from datetime import timezone, datetime
from pathlib import Path
from threading import BoundedSemaphore, RLock
from typing import Any, Literal
Expand Down Expand Up @@ -932,7 +932,7 @@ def public_forensic_log(
limit: int = Query(500, ge=1, le=5000),
):
"""Bounded PLOG/1 text projection for applications, operators and LLMs."""
selected_day = day or datetime.now(UTC).date().isoformat()
selected_day = day or datetime.now(timezone.utc).date().isoformat()
lines = get_planfile().store.forensic_log_lines(
date=selected_day,
ticket_id=ticket_id,
Expand Down Expand Up @@ -960,7 +960,7 @@ def public_forensic_log_json(
"""Parsed PLOG/1 records with the same bounded filters as the text file."""
from planfile.core.forensic_log_dsl import parse

selected_day = day or datetime.now(UTC).date().isoformat()
selected_day = day or datetime.now(timezone.utc).date().isoformat()
lines = get_planfile().store.forensic_log_lines(
date=selected_day,
ticket_id=ticket_id,
Expand Down Expand Up @@ -1574,7 +1574,7 @@ async def _watch_planfile_changes(interval_seconds: float = 3.0) -> None:
"type": "dashboard",
"action": "watch-error",
"ticket_id": "-",
"created_at": datetime.now(UTC).isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
"ticket": {"execution": {"state": "failed", "last_error": str(exc)}},
}
)
Expand All @@ -1596,7 +1596,7 @@ async def _watch_planfile_changes(interval_seconds: float = 3.0) -> None:
"type": "dashboard",
"action": "watch-error",
"ticket_id": "-",
"created_at": datetime.now(UTC).isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
"ticket": {"execution": {"state": "failed", "last_error": str(exc)}},
}
_remember_event(payload)
Expand All @@ -1618,7 +1618,7 @@ async def _archive_history_daily(interval_seconds: float = 300.0) -> None:
"""Run the idempotent history sweep once on startup and per UTC date."""
last_run_date = None
while True:
today = datetime.now(UTC).date()
today = datetime.now(timezone.utc).date()
if today != last_run_date:
try:
report = await asyncio.to_thread(
Expand All @@ -1630,7 +1630,7 @@ async def _archive_history_daily(interval_seconds: float = 300.0) -> None:
"type": "management.event",
"action": "daily-history-error",
"ticket_id": "-",
"created_at": datetime.now(UTC).isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
"source": "planfile",
"tool": "planfile.api",
"level": "error",
Expand All @@ -1647,7 +1647,7 @@ async def _archive_history_daily(interval_seconds: float = 300.0) -> None:
"type": "management.event",
"action": "daily-history",
"ticket_id": "-",
"created_at": datetime.now(UTC).isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
"source": "planfile",
"tool": "planfile.api",
"level": "info",
Expand Down Expand Up @@ -1750,7 +1750,7 @@ async def _start_planfile_watcher() -> None:
"type": "management.event",
"action": "started",
"ticket_id": "-",
"created_at": datetime.now(UTC).isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
"source": "planfile",
"tool": "planfile.api",
"queue": "koru-management",
Expand Down Expand Up @@ -1798,7 +1798,7 @@ def list_events(
@app.post("/events/test", tags=["events"])
async def create_test_event(body: TestEventRequest):
"""Broadcast a synthetic dashboard event without mutating a real ticket."""
created_at = datetime.now(UTC).isoformat()
created_at = datetime.now(timezone.utc).isoformat()
payload = {
"type": "dashboard.test",
"action": "error",
Expand Down Expand Up @@ -1831,7 +1831,7 @@ async def ingest_management_event(body: ManagementEventRequest):
"type": "management.event",
"action": body.action,
"ticket_id": ticket_id,
"created_at": datetime.now(UTC).isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
"source": body.source,
"tool": body.tool,
"queue": body.queue,
Expand Down Expand Up @@ -3549,7 +3549,7 @@ async def _broadcast_ticket_event(
"type": event_type,
"action": action,
"ticket_id": ticket.id if ticket is not None else ticket_id,
"created_at": datetime.now(UTC).isoformat(),
"created_at": datetime.now(timezone.utc).isoformat(),
}
if ticket is not None:
payload["ticket"] = ticket.model_dump(mode="json", exclude_none=True)
Expand Down
4 changes: 2 additions & 2 deletions planfile/core/models/ticket.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from __future__ import annotations

from datetime import UTC, datetime
from datetime import timezone, datetime
from typing import Any

from pydantic import BaseModel, Field
Expand All @@ -14,7 +14,7 @@


def _utcnow() -> datetime:
return datetime.now(UTC)
return datetime.now(timezone.utc)


class TicketSource(BaseModel):
Expand Down
6 changes: 3 additions & 3 deletions planfile/core/operational_dsl.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import hashlib
import json
import re
from datetime import UTC, datetime
from datetime import timezone, datetime
from typing import Any
from urllib.parse import quote, unquote

Expand Down Expand Up @@ -57,7 +57,7 @@ def redact(value: Any, key: str = "") -> Any:

def _text(value: Any, fallback: str = "-") -> str:
if isinstance(value, datetime):
value = value.astimezone(UTC).isoformat()
value = value.astimezone(timezone.utc).isoformat()
normalized = str(value if value is not None else "").strip()
return normalized or fallback

Expand All @@ -66,7 +66,7 @@ def create_event(**input: Any) -> dict[str, Any]:
data = redact(input.get("data") or {})
core = {
"schema": SCHEMA,
"timestamp": _text(input.get("timestamp") or datetime.now(UTC).isoformat()),
"timestamp": _text(input.get("timestamp") or datetime.now(timezone.utc).isoformat()),
"kind": _text(input.get("kind"), "operation"),
"source": _text(input.get("source"), "planfile"),
"ticket_id": _text(input.get("ticket_id")),
Expand Down
Loading
Loading