Document audit-log retention/persistence policy in SPEC.md and pin it in AuditLog #7

Closed
opened 2026-09-21 22:20:39 +00:00 by Mark · 2 comments
Owner

docs/ARCHITECTURE.md documents the action-layer chain ends with AuditLog.log_action(...) for every admin action (world edits, scripts compiled, points granted, players kicked). The architecture treats audit as load-bearing for the security model, but nothing pins:

  1. Where the log persists (file path, in-memory only?).
  2. How long it is retained.
  3. How operators query/export it.
  4. Whether it survives a server restart.

Without these pinned, an admin kick or a malicious script deployment leaves no auditable trail across restarts, which defeats the purpose.

What to do

  1. Inspect AuditLog.gd (currently unknown shape) to determine current persistence.
  2. Document in a new ## Audit log section in docs/ARCHITECTURE.md:
    • storage path (user://audit.log is the obvious default — confirm);
    • retention policy (recommend: rotated daily, 30-day default, configurable in server config;
    • rotation behavior (truncate vs rename-and-new-file);
    • query interface (CLI command, admin toolkit tab, or both).
  3. Pin the policy in code: add the documented defaults as constant fields on AuditLog (MAX_AGE_DAYS = 30, MAX_FILE_BYTES = 10_485_760) so the docs and code can't drift.
  4. Test the rotation path: tests/test_audit_log.gd writes an entry, asserts the file format, asserts rotation behavior.

Acceptance

docs/ARCHITECTURE.md has the ## Audit log section. AuditLog.gd exposes the retention knobs as constants. The new test passes. Existing CI stays green.

Labels

documentation, enhancement

`docs/ARCHITECTURE.md` documents the action-layer chain ends with `AuditLog.log_action(...)` for every admin action (world edits, scripts compiled, points granted, players kicked). The architecture treats audit as load-bearing for the security model, but nothing pins: 1. Where the log persists (file path, in-memory only?). 2. How long it is retained. 3. How operators query/export it. 4. Whether it survives a server restart. Without these pinned, an admin kick or a malicious script deployment leaves no auditable trail across restarts, which defeats the purpose. ## What to do 1. **Inspect** `AuditLog.gd` (currently unknown shape) to determine current persistence. 2. **Document** in a new `## Audit log` section in `docs/ARCHITECTURE.md`: - storage path (`user://audit.log` is the obvious default — confirm); - retention policy (recommend: rotated daily, 30-day default, configurable in server config; - rotation behavior (truncate vs rename-and-new-file); - query interface (CLI command, admin toolkit tab, or both). 3. **Pin** the policy in code: add the documented defaults as constant fields on `AuditLog` (`MAX_AGE_DAYS = 30`, `MAX_FILE_BYTES = 10_485_760`) so the docs and code can't drift. 4. **Test** the rotation path: `tests/test_audit_log.gd` writes an entry, asserts the file format, asserts rotation behavior. ## Acceptance `docs/ARCHITECTURE.md` has the `## Audit log` section. `AuditLog.gd` exposes the retention knobs as constants. The new test passes. Existing CI stays green. ## Labels `documentation`, `enhancement`
Author
Owner

Closed via commit f209c96 on main.

What landed:

  • scripts/sec/audit_log.gd — added MAX_AGE_DAYS=30, MAX_AGE_SECONDS, MAX_FILE_BYTES=10_485_760, LOG_PATH='user://audit.log', ROTATED_PATH='user://audit.log.1' as pinned constants; NDJSON append-only persistence at _log_path; single-generation rotation when the active file hits MAX_FILE_BYTES; in-memory pruning by BOTH count cap and age cap on every log_action(); constructor-time rehydration of the in-memory ring from the most recent MAX_ENTRIES lines (skipping anything past MAX_AGE_SECONDS) so audit history survives a server restart; time field switched to Time.get_unix_time_from_system() (absolute unix seconds).
  • docs/ARCHITECTURE.md — new ## Audit log section pinning storage path, retention policy, rotation behavior, and query interface in the same format operators read the constants in.
  • tests/unit/test_audit_log.gd + .tscn — 26 checks: constants pinned at expected values, NDJSON format one-line-per-entry, MAX_ENTRIES ring cap, MAX_AGE_SECONDS age drop, rotation at MAX_FILE_BYTES, default-path constructor, restart-survival rehydration, EventBus-absent graceful fallback.

Gates: verify.sh PASS, lint.sh PASS, all 6 unit suites + e2e + sandbox = 327 checks PASS.

Docs and code are mirrored: change a constant, change the same value in the ARCHITECTURE.md ## Audit log table in the same commit.

Closed via commit `f209c96` on main. What landed: - `scripts/sec/audit_log.gd` — added `MAX_AGE_DAYS=30`, `MAX_AGE_SECONDS`, `MAX_FILE_BYTES=10_485_760`, `LOG_PATH='user://audit.log'`, `ROTATED_PATH='user://audit.log.1'` as pinned constants; NDJSON append-only persistence at `_log_path`; single-generation rotation when the active file hits `MAX_FILE_BYTES`; in-memory pruning by BOTH count cap and age cap on every `log_action()`; constructor-time rehydration of the in-memory ring from the most recent `MAX_ENTRIES` lines (skipping anything past `MAX_AGE_SECONDS`) so audit history survives a server restart; `time` field switched to `Time.get_unix_time_from_system()` (absolute unix seconds). - `docs/ARCHITECTURE.md` — new `## Audit log` section pinning storage path, retention policy, rotation behavior, and query interface in the same format operators read the constants in. - `tests/unit/test_audit_log.gd` + `.tscn` — 26 checks: constants pinned at expected values, NDJSON format one-line-per-entry, `MAX_ENTRIES` ring cap, `MAX_AGE_SECONDS` age drop, rotation at `MAX_FILE_BYTES`, default-path constructor, restart-survival rehydration, EventBus-absent graceful fallback. Gates: `verify.sh` PASS, `lint.sh` PASS, all 6 unit suites + e2e + sandbox = 327 checks PASS. Docs and code are mirrored: change a constant, change the same value in the ARCHITECTURE.md `## Audit log` table in the same commit.
Mark closed this issue 2026-09-22 16:44:30 +00:00
Author
Owner

Closed via commit f209c96 on main.

What landed:

  • scripts/sec/audit_log.gd — added MAX_AGE_DAYS=30, MAX_AGE_SECONDS, MAX_FILE_BYTES=10_485_760, LOG_PATH='user://audit.log', ROTATED_PATH='user://audit.log.1' as pinned constants; NDJSON append-only persistence at _log_path; single-generation rotation when the active file hits MAX_FILE_BYTES; in-memory pruning by BOTH count cap and age cap on every log_action(); constructor-time rehydration of the in-memory ring from the most recent MAX_ENTRIES lines (skipping anything past MAX_AGE_SECONDS) so audit history survives a server restart; time field switched to Time.get_unix_time_from_system() (absolute unix seconds).
  • docs/ARCHITECTURE.md — new ## Audit log section pinning storage path, retention policy, rotation behavior, and query interface in the same format operators read the constants in.
  • tests/unit/test_audit_log.gd + .tscn — 26 checks: constants pinned at expected values, NDJSON format one-line-per-entry, MAX_ENTRIES ring cap, MAX_AGE_SECONDS age drop, rotation at MAX_FILE_BYTES, default-path constructor, restart-survival rehydration, EventBus-absent graceful fallback.

Gates: verify.sh PASS, lint.sh PASS, all 6 unit suites + e2e + sandbox = 327 checks PASS.

Docs and code are mirrored: change a constant, change the same value in the ARCHITECTURE.md ## Audit log table in the same commit.

Closed via commit `f209c96` on main. What landed: - `scripts/sec/audit_log.gd` — added `MAX_AGE_DAYS=30`, `MAX_AGE_SECONDS`, `MAX_FILE_BYTES=10_485_760`, `LOG_PATH='user://audit.log'`, `ROTATED_PATH='user://audit.log.1'` as pinned constants; NDJSON append-only persistence at `_log_path`; single-generation rotation when the active file hits `MAX_FILE_BYTES`; in-memory pruning by BOTH count cap and age cap on every `log_action()`; constructor-time rehydration of the in-memory ring from the most recent `MAX_ENTRIES` lines (skipping anything past `MAX_AGE_SECONDS`) so audit history survives a server restart; `time` field switched to `Time.get_unix_time_from_system()` (absolute unix seconds). - `docs/ARCHITECTURE.md` — new `## Audit log` section pinning storage path, retention policy, rotation behavior, and query interface in the same format operators read the constants in. - `tests/unit/test_audit_log.gd` + `.tscn` — 26 checks: constants pinned at expected values, NDJSON format one-line-per-entry, `MAX_ENTRIES` ring cap, `MAX_AGE_SECONDS` age drop, rotation at `MAX_FILE_BYTES`, default-path constructor, restart-survival rehydration, EventBus-absent graceful fallback. Gates: `verify.sh` PASS, `lint.sh` PASS, all 6 unit suites + e2e + sandbox = 327 checks PASS. Docs and code are mirrored: change a constant, change the same value in the ARCHITECTURE.md `## Audit log` table in the same commit.
Sign in to join this conversation.
No description provided.