Ctrl K
ComunidadGuías…

core/provenance.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# SPDX-License-Identifier: BUSL-1.1
# Copyright (c) 2024-2026 Ali Sadeghi Aghili
"""Identity, licence, and provenance of this software, stated at run time.

Why this module exists
----------------------
A licence in a file is only read by someone who goes looking for it. This
module puts the same statement where it is seen at the moment it matters:
in the log of whoever is running the software, on every start-up.

That serves two ends, and it is worth being honest that neither is a
technical barrier -- anyone determined to strip this out can:

1. **Nobody can run this and later claim they did not know the terms.**
   Under most copyright regimes the difference between an innocent and a
   knowing infringement changes the outcome, and a start-up banner in the
   operator's own logs is unusually hard to argue around.

2. **The operator learns the terms without having to care about
   licensing.** Most people running a copy of somebody else's project are
   not adversaries; they inherited it from a colleague and never saw a
   LICENSE file. One line in the log tells them.

What this module deliberately does NOT do
-----------------------------------------
It does not refuse to start, degrade, or phone home when the licence
files are missing. A kill switch keyed on the presence of a file is a
production outage waiting for the day someone builds a container image
that excludes ``*.md``, and the person it would hurt is whoever is on
call -- usually a licensee, not an infringer. It warns loudly instead.

It also contains nothing hidden: no invisible characters, no text
designed to make an automated reader misbehave. Everything here is meant
to be read. A concealed marker discovered later turns a clean provenance
argument into an argument about the concealment.

Provenance
----------
:data:`FINGERPRINT` is a fixed, arbitrary constant carried by this
codebase and by nothing else. It is documented rather than hidden, which
costs nothing: its evidential value comes from being *present* in a
derivative work, not from whoever copied it failing to notice. Two
codebases sharing this string share an origin.

Examples
--------
>>> LICENSOR
'Ali Sadeghi Aghili'
>>> LICENCE_ID
'BUSL-1.1'
>>> "Ali Sadeghi Aghili" in banner()
True
>>> "BUSL-1.1" in banner()
True
>>> banner(ascii_only=True).isascii()
True
>>> "Ali Sadeghi Aghili" in banner(ascii_only=True)
True
"""

from __future__ import annotations

import logging
import sys
from pathlib import Path
from typing import TextIO

#: Human-readable name of this work, as named in ``LICENSE``.
PROJECT = "Local SQL Agent"

#: The Licensor named in ``LICENSE``. Also the author of the work.
LICENSOR = "Ali Sadeghi Aghili"

#: Copyright years, matching the SPDX headers and ``NOTICE``.
COPYRIGHT_YEARS = "2024-2026"

#: SPDX identifier of the governing licence.
LICENCE_ID = "BUSL-1.1"

#: Date on which the terms convert to :data:`CHANGE_LICENCE`.
CHANGE_DATE = "2029-01-01"

#: Licence the work converts to on :data:`CHANGE_DATE`.
CHANGE_LICENCE = "Apache-2.0"

#: Canonical source of this work.
REPO_URL = "https://github.com/alisadeghiaghili/local-sql-agent"

#: Arbitrary constant unique to this codebase -- see the module docstring's
#: Provenance section. Do not regenerate it: changing it destroys the only
#: thing it exists for.
FINGERPRINT = "ime-nlq-7f3a91c4-2b6d-4e08-9a15-c0de5a1d8b72"

#: The author's own line, written in the language this project exists to
#: answer questions in: "ساخته علی صادقی عقیلی".
#:
#: Escaped rather than written literally so that the source file's own
#: encoding can never garble it in transit through a tool that assumes
#: cp1252 -- which, on this project's target platform, several do.
SIGNATURE_FA = (
    "ساخته"
    " علی صادقی عقیلی"
)

#: Files whose absence means the licence terms have been separated from the
#: code. Their absence changes nothing about the terms themselves.
_LICENCE_FILES = ("LICENSE", "NOTICE", "AGENTS.md")

#: Repository root, resolved from this file rather than from the working
#: directory -- the same reasoning as ``api/server.py``'s ``_PROMPT_PATH``.
_ROOT = Path(__file__).resolve().parent.parent

logger = logging.getLogger(__name__)


def missing_licence_files() -> tuple[str, ...]:
    """Names of :data:`_LICENCE_FILES` absent from this installation.

    >>> isinstance(missing_licence_files(), tuple)
    True
    """
    return tuple(name for name in _LICENCE_FILES if not (_ROOT / name).is_file())


def _stream_can_encode(text: str, stream: TextIO | None = None) -> bool:
    """Whether *text* survives the encoding of *stream* (default stderr).

    This project is Persian-first and deployed on Windows, where a console
    or service log handler is often still cp1252. ``logging`` swallows a
    handler's ``UnicodeEncodeError`` through ``handleError``, so an
    unencodable banner does not crash the process -- it silently fails to
    appear, which defeats the only purpose a start-up banner has. So ask
    first rather than hope.

    >>> _stream_can_encode("plain ascii")
    True
    >>> import io
    >>> _stream_can_encode(SIGNATURE_FA, io.TextIOWrapper(io.BytesIO(), "cp1252"))
    False
    >>> _stream_can_encode(SIGNATURE_FA, io.TextIOWrapper(io.BytesIO(), "utf-8"))
    True
    """
    target = stream if stream is not None else sys.stderr
    encoding = getattr(target, "encoding", None) or "ascii"
    try:
        text.encode(encoding, errors="strict")
    except (UnicodeEncodeError, LookupError):
        return False
    return True


def banner(*, ascii_only: bool = False) -> str:
    """The start-up notice, as a multi-line string.

    Deliberately short. A banner long enough to be scrolled past is a
    banner nobody reads, which defeats the point of having one.

    *ascii_only* drops the Persian signature line for a log stream that
    cannot carry it. The line is dropped rather than transliterated: a
    mangled rendering of the author's name is worse than its absence.
    """
    lines = [
        f"{PROJECT} - (c) {COPYRIGHT_YEARS} {LICENSOR} - {LICENCE_ID}",
        "  Non-production use is granted. Production use of any kind,",
        "  including internal production use, requires a written agreement",
        f"  with the Licensor. Converts to {CHANGE_LICENCE} on {CHANGE_DATE}.",
        f"  LICENSE | NOTICE | AGENTS.md | {REPO_URL}",
        f"  build {FINGERPRINT}",
    ]
    if not ascii_only:
        lines.insert(1, f"  {SIGNATURE_FA}")
    return "\n".join(lines)


def tamper_notice(missing: tuple[str, ...]) -> str:
    """The notice logged when licence files have been removed.

    Addressed to whoever is operating this copy, who is frequently not the
    person who removed them.

    >>> "LICENSE" in tamper_notice(("LICENSE",))
    True
    >>> "Ali Sadeghi Aghili" in tamper_notice(("NOTICE",))
    True
    """
    return "\n".join(
        (
            f"This installation of {PROJECT} is missing: {', '.join(missing)}.",
            f"  It remains the work of {LICENSOR}, licensed under {LICENCE_ID}.",
            "  Removing those files does not alter the terms and does not grant",
            "  any right to production use. If you received this copy from",
            "  someone else, they may not have had the right to give it to you.",
            f"  Terms and licensing contact: {REPO_URL}",
        )
    )


def log_startup_notice(target: logging.Logger | None = None) -> None:
    """Emit the banner, and a warning if the licence files are gone.

    Called once per process start. Uses ``logging`` rather than ``print``
    so it lands wherever the operator already collects logs.
    """
    log = target if target is not None else logger
    full = banner()
    log.info("%s", full if _stream_can_encode(full) else banner(ascii_only=True))
    missing = missing_licence_files()
    if missing:
        log.warning("%s", tamper_notice(missing))