oct.git.audit module#
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 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
AuditRecord— the exact on-disk schema (blueprint §12.2, verbatim).Implement
AuditLoggerwith 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
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 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.AuditLogger.write()must never raise. On any IO failure it logs an error via_dbgand returns silently — the audit log is observability, not a correctness path.Filenames follow
git-audit-YYYYMMDD-HHMMSS.jsonlstrictly so that lexical ordering matches chronological ordering for pruning.Every
AuditRecordfield is JSON-serialisable.argsis always passed through credential redaction before serialisation.
- oct.git.audit.AUDIT_MAX_FILES: int = 20#
Maximum number of
git-audit-*.jsonlfiles kept on disk. When the 21st file is created by a rotation event, the oldest is deleted.
- oct.git.audit.AUDIT_MAX_SIZE_BYTES: int = 5242880#
Rotation threshold. When the current audit file reaches this size in bytes, the next
AuditLogger.write()call closes it and opens a new timestamped file.
- class oct.git.audit.AuditLogger(project_root: Path)[source]#
Bases:
objectAppend-only rotating JSONL logger for
oct gitcommands.One instance corresponds to one project root. The constructor does not open a file — the first
write()call lazily materialises thelogs/directory and the current audit file. This lets a command that never reaches the write stage (e.g. a CLI parse error) leave no side-effects on disk.Rotation strategy#
Size rotation: before each write, if the current file is at or above
AUDIT_MAX_SIZE_BYTES, close it and open a new timestamped file. The rotating write itself goes into the new file, not the old one.Count pruning: after any rotation, if the number of
git-audit-*.jsonlfiles in the logs directory exceedsAUDIT_MAX_FILES, delete the oldest (lexical order) until the count is within the limit.
Error handling#
Any IO error during
write()— creating the directory, opening the file, serialising the record, writing the line, or rotating — is caught and logged via_dbg("GIT", ..., 1). The method never raises; audit logging is best-effort observability and must not mask command failures.- property current_path: Path | None#
Current audit filename (
Nonebefore first write).
- write(record: AuditRecord) None[source]#
Serialise
recordand append it as one JSONL line.Never raises. On IO failure, logs an error via
_dbgat level 1 and returns. The caller is never blocked by audit log misbehaviour.
- class oct.git.audit.AuditRecord(timestamp: str = '', command: str = '', args: list[str] = <factory>, branch: str | None = None, staged_files: int = 0, checks_passed: bool = False, secrets_detected: bool = False, lint_violations: int = 0, format_violations: int = 0, exit_code: int | None = None, duration_ms: int = 0, per_subproject_results: list[dict] = <factory>)[source]#
Bases:
objectSchema for one audit row. Serialised via
dataclasses.asdict().Every field is either JSON-primitive or a list/dict of JSON-primitives so that
json.dumps(asdict(record))is guaranteed to succeed.argsis credential-redacted by the caller before being assigned (see_redact_args()).Fields whose value is unknown at record-construction time may be left at their default; missing fields are emitted as
nullin the JSON output.- args: list[str]#
- branch: str | None = None#
- checks_passed: bool = False#
- command: str = ''#
- duration_ms: int = 0#
- exit_code: int | None = None#
- format_violations: int = 0#
- lint_violations: int = 0#
- per_subproject_results: list[dict]#
per-subproject quality-gate results when oct git commit ran a workspace fan-out. Empty list when fan-out was not used. Each entry: {name, profile, files, lint_violations, format_violations, secrets}.
- Type:
F1.3
- secrets_detected: bool = False#
- staged_files: int = 0#
- timestamp: str = ''#
- oct.git.audit.audited(cmd_name: str) Callable[[Callable[[...], Any]], Callable[[...], Any]][source]#
Decorator that wraps an
oct gitClick command.Responsibilities:
Emit
_dbg("GIT", "command_start ...", 4)before the command.Run the wrapped function.
In a
finallyblock, emit_dbg("GIT", "command_end ...", 4)and write anAuditRecordinto 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 (withexit_code=Noneandchecks_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".