Ë
    ÛJêj—.  ã                  ón  — U d Z ddlmZ ddlZddlZddlmZ ddlmZ ddl	m
Z
 g d¢Z ej                  e«      ZdZd	ed
<   dZd	ed<   dZd	ed<   dZd	ed<   dZd	ed<   dZded<   dZd	ed<   d&d„Zd'd(d„Zd'd)d„Z	 d'ddœ	 	 	 	 	 d*d„Zefd+d„Zdddddœ	 	 	 	 	 	 	 	 	 d,d „Zd'd)d!„Zd'd)d"„Zd'd-d#„Zdd$œd.d%„Z y)/aÂ  Resolution and opening of the local (on-disk) store locations.

This module only resolves filesystem locations and opens the local SQLite store.
It never grants permissions, never changes the policy mode, and never disables
the sandbox. Authorization, policy decisions, and sandbox enforcement remain
the responsibility of the security plane.

Precedence for the data directory is: explicit argument, then the
``CYBERBRAIN_DATA_DIR`` environment variable, then the platform-specific user
data directory. Precedence for the database file is: explicit argument, then
``CYBERBRAIN_DB_PATH``, then ``<data dir>/cyberbrain.sqlite3``.

The workspaces directory (``<data dir>/workspaces``) is the *sanctioned root*
for host directories that the execution sandbox may bind read-write.
:func:`resolve_workspace` is the single containment check against that root: a
workspace outside it is refused, because a read-write bind mount of an
arbitrary host directory would hand sandboxed code the whole host regardless
of how well the rest of the sandbox is hardened.

Retention configuration is fail-closed: a malformed ``CYBERBRAIN_RETENTION_DAYS``
value raises :class:`ValueError` instead of silently falling back to the default.
é    )ÚannotationsN)ÚPath)Úuser_data_dir)ÚSQLiteDatabase)ÚDEFAULT_DB_FILENAMEÚDEFAULT_RETENTION_DAYSÚENV_DATA_DIRÚENV_DB_PATHÚENV_RETENTION_DAYSÚENV_WORKSPACES_ROOTÚensure_data_dirÚensure_workspaces_rootÚopen_databaseÚresolve_data_dirÚresolve_db_pathÚresolve_workspaceÚretention_days_from_envÚworkspaces_rootÚCYBERBRAIN_DATA_DIRÚstrr	   ÚCYBERBRAIN_DB_PATHr
   ÚCYBERBRAIN_RETENTION_DAYSr   ÚCYBERBRAIN_WORKSPACES_ROOTr   zcyberbrain.sqlite3r   éZ   Úintr   Ú
workspacesÚ_WORKSPACES_DIRNAMEc           	     óž   — t        t        j                  j                  t        j                  j	                  t        | «      «      «      «      S )zHExpand ``~`` and environment references without touching the filesystem.)r   ÚosÚpathÚ
expandvarsÚ
expanduserr   )r    s    úF/home/oem/CyberBrain26-unified/src/cyberbrain26/product/local_store.pyÚ_expandr$   @   s/   € ä”—‘×"Ñ"¤2§7¡7×#5Ñ#5´c¸$³iÓ#@ÓAÓBÐBó    c                ó4  — | �t        | «      j                  «       S t        j                  j	                  t
        «      }|�7|j                  «       r't        |j                  «       «      j                  «       S t        t        d«      «      j                  «       S )a  Resolve the base data directory.

    Precedence: ``explicit`` argument, then the ``CYBERBRAIN_DATA_DIR``
    environment variable, then :func:`platformdirs.user_data_dir`. The returned
    path is absolute and expanded, but the directory is **not** created as a
    side effect of this call; creation happens in :func:`ensure_data_dir` or
    :func:`open_database`.

    Args:
        explicit: Optional explicit directory overriding all other sources.

    Returns:
        The resolved, absolute data directory path. It need not exist.
    ÚCyberBrain26)r$   Úresolver   ÚenvironÚgetr	   Ústripr   )ÚexplicitÚ	env_values     r#   r   r   E   ss   € ð ÐÜ�xÓ ×(Ñ(Ó*Ð*Ü—
‘
—‘œ|Ó,€IØÐ §¡Ô!2Ü�y—‘Ó(Ó)×1Ñ1Ó3Ð3Ü”= Ó0Ó1×9Ñ9Ó;Ð;r%   c                ó°   — t        | «      }|j                  «       }|j                  ddd¬«       |s	 |j                  d«       |S |S # t        $ r Y |S w xY w)a  Resolve the data directory and create it with owner-only permissions.

    Args:
        data_dir: Optional explicit directory; see :func:`resolve_data_dir`.

    Returns:
        The resolved data directory, created with mode ``0o700`` where the
        platform supports it.
    TéÀ  ©ÚparentsÚexist_okÚmode)r   ÚexistsÚmkdirÚchmodÚOSError)Údata_dirÚresolvedÚexisteds      r#   r   r   \   sd   € ô   Ó)€HØ�o‰oÓ€GØ‡N�N˜4¨$°U€NÔ;Ùð	Ø�N‰N˜5Ô!ð €Oˆ8€Oøô ò 	ØØ€Oð	ús   ³A Á	AÁA©r8   c               óÜ  — | �Lt        | «      dk(  st        | «      j                  d«      rt        d«      ‚t        | «      j	                  «       S t
        j                  j                  t        «      }|�Z|j                  «       rJ|j                  «       }|dk(  s|j                  d«      rt        d«      ‚t        |«      j	                  «       S t        |«      t        z  j	                  «       S )aU  Resolve the SQLite database file path.

    Precedence: ``explicit`` argument, then the ``CYBERBRAIN_DB_PATH``
    environment variable, then ``<data dir>/cyberbrain.sqlite3``. A non-``None``
    ``data_dir`` argument takes precedence over the ``CYBERBRAIN_DATA_DIR``
    environment variable but never overrides an explicit ``explicit`` path.

    Args:
        explicit: Optional explicit database file path with highest precedence.
        data_dir: Optional data directory used to derive the default filename.

    Returns:
        The resolved, absolute database path. It need not exist.
    z:memory:zfile:z.SQLiteDatabase requires a persistent file path)r   Ú
startswithÚ
ValueErrorr$   r(   r   r)   r*   r
   r+   r   r   )r,   r8   r-   Ú	candidates       r#   r   r   q   sÅ   € ð& Ðäˆx‹=˜JÒ&¬#¨h«-×*BÑ*BÀ7Ô*KÜÐMÓNÐNÜ�xÓ ×(Ñ(Ó*Ð*Ü—
‘
—‘œ{Ó+€IØÐ §¡Ô!2Ø—O‘OÓ%ˆ	ð ˜
Ò" i×&:Ñ&:¸7Ô&CÜÐMÓNÐNÜ�yÓ!×)Ñ)Ó+Ð+Ü˜XÓ&Ô)<Ñ<×EÑEÓGÐGr%   c                ó*  — t         j                  j                  t        «      }|�|j	                  «       s| S |j	                  «       }	 t        |«      }|dk  rt        t        › d|›�«      ‚|S # t        $ r}t        t        › d|›�«      |‚d}~ww xY w)a  Read the retention window from the environment, fail-closed.

    Unset or empty values yield ``default``. Any present value that is not a
    positive integer raises :class:`ValueError`; a malformed value never falls
    back to the default silently.

    Args:
        default: Value returned when the environment variable is unset or empty.

    Returns:
        The configured retention window in days.

    Raises:
        ValueError: If the variable is set to a non-integer or non-positive
            value.
    Nz! must be a positive integer, got r   z must be positive, got )r   r)   r*   r   r+   r   r>   )ÚdefaultÚrawÚtextÚvalueÚexcs        r#   r   r   •   s›   € ô" �*‰*�.‰.Ô+Ó
,€CØ
€{˜#Ÿ)™)œ+ØˆØ�9‰9‹;€DðÜ�D“	ˆð
 �‚zÜÔ.Ð/Ð/FÀsÀgÐNÓOÐOØ€Løô ò ÜÜ!Ð"Ð"CÀCÀ7ÐKó
àð	ûðús   Á	A/ Á/	BÁ8BÂBT)Údb_pathr8   ÚpersistÚretention_daysc                ó”   — |syt        | |¬«      }|j                  j                  ddd¬«       |€
t        «       n|}t	        ||¬«      S )a  Open the local SQLite store, creating its parent directory if needed.

    When ``persist`` is ``False`` the call is a no-op that touches no files and
    no directories; it returns ``None`` instead of a database handle.

    Args:
        db_path: Optional explicit database path; see :func:`resolve_db_path`.
        data_dir: Optional data directory used when ``db_path`` is not given.
        persist: When ``False``, return ``None`` without touching the filesystem.
        retention_days: Optional explicit retention window. Defaults to
            :func:`retention_days_from_env`.

    Returns:
        The opened :class:`SQLiteDatabase`, or ``None`` when ``persist`` is
        ``False``.

    Raises:
        ValueError: If the retention window is malformed or non-positive.
    Nr;   Tr/   r0   )rH   )r   Úparentr5   r   r   )rF   r8   rG   rH   r    Údayss         r#   r   r   µ   sL   € ñ4 ØÜ˜7¨XÔ6€DØ‡K�K×Ñ˜d¨T¸ÐÔ>Ø(6Ð(>Ô"Ô$ÀN€DÜ˜$¨tÔ4Ð4r%   c                óB   — t        | «      t        z  j                  «       S )a  Return the workspaces directory inside the data directory.

    The directory is **not** created here; callers create it lazily with
    :func:`ensure_workspaces_root`.

    The sanctioned root is deliberately derived from :func:`resolve_data_dir`
    instead of being configured separately, so there is exactly one source of
    truth for "where CyberBrain26 stores its things". Deployments that need a
    different root set ``CYBERBRAIN_WORKSPACES_ROOT`` (or pass the root
    explicitly to the caller); that is an explicit decision to widen the
    sandbox, never a silent fallback.

    Args:
        data_dir: Optional explicit data directory; see :func:`resolve_data_dir`.

    Returns:
        The resolved ``<data dir>/workspaces`` path. It need not exist.
    )r   r   r(   r;   s    r#   r   r   ×   s   € ô& ˜XÓ&Ô)<Ñ<×EÑEÓGÐGr%   c                óì   — t        | «      }|j                  «       }|j                  ddd¬«       |s	 |j                  d«       |S |S # t        $ r"}t
        j                  d||«       Y d}~|S d}~ww xY w)a   Resolve the workspaces root and create it with owner-only permissions.

    Args:
        data_dir: Optional explicit data directory; see :func:`resolve_data_dir`.

    Returns:
        The resolved workspaces root, created with mode ``0o700`` where the
        platform supports it.
    Tr/   r0   z8could not restrict permissions of workspaces root %s: %sN)r   r4   r5   r6   r7   ÚloggerÚwarning)r8   r9   r:   rE   s       r#   r   r   í   s   € ô ˜xÓ(€HØ�o‰oÓ€GØ‡N�N˜4¨$°U€NÔ;Ùð	Ø�N‰N˜5Ô!ð €Oˆ8€Oøô ò 	ô �N‰NØJÈHÐVY÷ð ð €Oûð	ús   ³A Á	A3ÁA.Á.A3c                óÊ   — |�'t        |«      j                  «       j                  «       n	t        «       }t	        | «      j                  «       }||k(  xs |j                  |«      S )a1  Report whether ``path`` resolves inside the sanctioned workspaces root.

    Both sides are resolved, so symlinks and ``..`` segments are collapsed
    before the comparison: a symlink inside the root that points at ``/etc``
    resolves to ``/etc`` and is therefore *not* contained.

    Args:
        path: Candidate path, typically a workspace taken from a step payload.
        root: Optional root to compare against; defaults to
            :func:`workspaces_root`.

    Returns:
        ``True`` when the resolved path is the root itself or below it.
    )r   r"   r(   r   r$   Úis_relative_to)r    ÚrootÚallowedr?   s       r#   Úis_within_workspaces_rootrT     sV   € ð 48Ð3CŒd�4‹j×#Ñ#Ó%×-Ñ-Ô/ÌÓIZ€GÜ˜“×%Ñ%Ó'€IØ˜ÑÒD 9×#;Ñ#;¸GÓ#DÐDr%   )rR   c               óî   — |�'t        |«      j                  «       j                  «       n	t        «       }t	        | «      j                  «       }||k7  r#|j                  |«      st        d|› d|› d�«      ‚|S )a&  Resolve a workspace path and refuse it when it escapes the workspaces root.

    This is the containment check every read-write workspace mount has to pass.
    It is deliberately small and lives next to :func:`workspaces_root` so that
    the sandbox provider and any future policy check share one definition of
    "a workspace we are willing to hand to sandboxed code".

    Args:
        workspace: Candidate workspace path from a step payload.
        root: Optional root to confine to; defaults to :func:`workspaces_root`.

    Returns:
        The resolved workspace path, which is inside ``root``.

    Raises:
        ValueError: If the resolved workspace is outside ``root``. The message
            names both the offending path and the allowed root so the refusal
            is auditable.
    z
workspace z( is outside the allowed workspaces root z&; refusing to bind-mount it read-write)r   r"   r(   r   r$   rQ   r>   )Ú	workspacerR   rS   r?   s       r#   r   r     s   € ð( 48Ð3CŒd�4‹j×#Ñ#Ó%×-Ñ-Ô/ÌÓIZ€GÜ˜	Ó"×*Ñ*Ó,€IØ�GÒ I×$<Ñ$<¸WÔ$EÜØ˜˜Ð#KÈGÈ9ð U3ð 3ó
ð 	
ð Ðr%   )r    ú
str | PathÚreturnr   )N)r,   ústr | Path | NonerX   r   )r8   rY   rX   r   )r,   rY   r8   rY   rX   r   )rA   r   rX   r   )
rF   rY   r8   rY   rG   ÚboolrH   z
int | NonerX   zSQLiteDatabase | None)r    rW   rR   rY   rX   rZ   )rV   rW   rR   rY   rX   r   )!Ú__doc__Ú
__future__r   Úloggingr   Úpathlibr   Úplatformdirsr   Ú$cyberbrain26.data_plane.sqlite_storer   Ú__all__Ú	getLoggerÚ__name__rN   r	   Ú__annotations__r
   r   r   r   r   r   r$   r   r   r   r   r   r   r   rT   r   © r%   r#   ú<module>rf      s7  ðòõ. #ã Û 	Ý å &å ?ò€ð" 
ˆ×	Ñ	˜8Ó	$€à)€ˆcÓ )Ø'€ˆSÓ 'Ø5Ð �CÓ 5Ø7Ð �SÓ 7à/Ð �SÓ /Ø Ð ˜Ó  à'Ð �SÓ 'óCô
<ô.ð, #'ð!Hð #'ñ!HØð!Hð  ð!Hð 
ó	!HðH ,Bô ðD "&Ø"&ØØ!%ñ5àð5ð  ð5ð ð	5ð
 ð5ð ó5ôDHô,ô4Eð( KOö r%   