Purpose
-------
Audit JSONL logger and AI-Trace decorator for Phase 4B ``oct git``
commands. Writes one JSON object per line to
``<project>/logs/git-audit-YYYYMMDD-HHMMSS.jsonl`` with size + count
rotation, and provides the :func:`audited` decorator that every
``oct git`` Click command is wrapped in so that entry/exit trace events
and a persistent audit row are recorded even if the command crashes.
Responsibilities
----------------
- Define :class:`AuditRecord` - the exact on-disk schema (blueprint
§12.2, verbatim).
- Implement :class:`AuditLogger` with size-based rotation (5 MB) and
count-based pruning (keep the 20 newest files).
- Redact credentials from any string value written to the log by
deferring to :func:`oct.core.git._redact_git_url`.
- Emit AI-Trace shadow events via ``_dbg("GIT", msg, 4)`` - file-only,
off by default; see blueprint §12.3 and Phase 4B design decision D7.
Diagnostics
-----------
Domain: GIT
Levels:
L1 - errors: rotation IO failures, directory-create failures
L2 - lifecycle: file rotated, files pruned
L3 - semantic: the record written, by command and outcome
L4 - deep trace: every ``write()`` call and every command start/end
Contracts
---------
- This module does not import ``click``, does not shell out, and does
not touch any git state. It only reads config-less arguments passed
in from the caller.
- :meth:`AuditLogger.write` must never raise. On any IO failure it
logs an error via ``_dbg`` and returns silently - the audit log is
*observability*, not a correctness path.
- Filenames follow ``git-audit-YYYYMMDD-HHMMSS.jsonl`` strictly so that
lexical ordering matches chronological ordering for pruning.
- Every ``AuditRecord`` field is JSON-serialisable. ``args`` is always
passed through credential redaction before serialisation.
Dependencies
------------
- oct.core.diagnostics (_dbg structured logger)
- oct.core.git (_redact_git_url - credential redaction of argv values)
- oct.core.option_c_dir (resolve_logs_dir - the audit log directory)
- oct.core.project_root (lazy; get_project_root - the project root when the Click context carries none)
- click (lazy; imported in the audited command wrapper, though no click name is used)
| Callable[[Callable[..., Any]], Callable[..., Any]] oct.git.audit.audited |
( |
str | cmd_name | ) |
|
Decorator that wraps an ``oct git`` Click command.
Responsibilities:
1. Emit ``_dbg("GIT", "command_start ...", 4)`` before the command.
2. Run the wrapped function.
3. In a ``finally`` block, emit ``_dbg("GIT", "command_end ...", 4)``
and write an :class:`AuditRecord` into the project's audit log.
The wrapped command is responsible for populating
``ctx.obj["_audit_record"]`` during its run - the decorator reads
that slot at the end and writes whatever is there, falling back to
a minimal record (with ``exit_code=None`` and ``checks_passed=False``)
if the command crashed before populating it. This guarantees every
command invocation leaves exactly one audit row.
Parameters
----------
cmd_name
Human-readable command identifier for the trace and audit row,
e.g. ``"oct git check"``.
Definition at line 337 of file audit.py.