security: add worker audit lifecycle records

ober

39692be15a438bc029dcf340ba62dbf8c4f1d16c

diff --git a/docs/kimi3-security-recommmendations.md b/docs/kimi3-security-recommmendations.md
index 476c109..925d607 100644
--- a/docs/kimi3-security-recommmendations.md
+++ b/docs/kimi3-security-recommmendations.md
@@ -341,8 +341,9 @@ the code that ships. This violates the repo's own pre-commit rule.
 ### K3-P0-02 — Build the exec-based confined worker (`(std security worker)`)
 **Serves:** G2, G3, G4. **Effort:** 1–2 weeks. **Status:** initial
 `(std security worker)` facade landed 2026-07-27 with `tests/test-worker.ss`;
-native pre-exec controls, egress proxy wiring, memory rlimit, and audit-log
-events remain open.
+audit-log start/end records landed 2026-07-27 and are asserted by
+`tests/test-worker.ss`. Native pre-exec controls, egress proxy wiring, and
+memory rlimit remain open.
 
 Every security doc routes adversarial work to "a bounded, separately exec'd
 worker". The initial facade exists; finish it as the assembly point for
diff --git a/docs/security-reference.md b/docs/security-reference.md
index c6fe23b..5694e77 100644
--- a/docs/security-reference.md
+++ b/docs/security-reference.md
@@ -249,10 +249,11 @@ returns `launched? = #f`, status `126`, and the refused axis list instead of
 silently running with weaker controls.
 
 Current limitation: the worker has a real exec boundary, pure environment,
-parent deadline, process-group kill through `aproc`, and returned output caps.
-The remaining pre-exec kernel-control backend is not complete, so rlimit,
-Landlock/seccomp, Seatbelt/Capsicum installation, deny-default egress proxy
-wiring, and audit-log start/end records remain tracked by the K3 handoff.
+parent deadline, process-group kill through `aproc`, returned output caps, and
+audit-log start/end records in `worker-result-diagnostics`. The remaining
+pre-exec kernel-control backend is not complete, so rlimit, Landlock/seccomp,
+Seatbelt/Capsicum installation, and deny-default egress proxy wiring remain
+tracked by the K3 handoff.
 
 ---
 
diff --git a/lib/std/security/worker.ss b/lib/std/security/worker.ss
index bdb3429..9c709f8 100644
--- a/lib/std/security/worker.ss
+++ b/lib/std/security/worker.ss
@@ -42,6 +42,13 @@
           (only (jerboa core) def defstruct)
           (only (std os aproc) aproc-run/status*)
           (only (std os limits sandbox) sandbox-capabilities sandbox-backend)
+          (only (std security audit-log)
+                audit-log
+                audit-log-now
+                audit-log-record!
+                audit-log-records
+                audit-process-start
+                audit-process-exit)
           (only (std security env)
                 env-policy?
                 env-policy-default
@@ -188,12 +195,24 @@
                     (apply aproc-run/status* command args)])
         (list stdout stderr status))))
 
-  (def (worker-finish-result command pol caps captured)
+  (def (worker-finish-result command pol caps captured log start-ms)
     (let* ([stdout (car captured)]
            [stderr (cadr captured)]
            [status (caddr captured)]
            [stdout* (truncate-string stdout (worker-policy-stdout-cap-bytes pol))]
-           [stderr* (truncate-string stderr (worker-policy-stderr-cap-bytes pol))])
+           [stderr* (truncate-string stderr (worker-policy-stderr-cap-bytes pol))]
+           [stdout-truncated? (truncated? stdout stdout*)]
+           [stderr-truncated? (truncated? stderr stderr*)])
+      (audit-log-record!
+       log
+       (audit-process-exit
+        'status: status
+        'elapsed-ms: (max 0 (- (audit-log-now) start-ms))
+        'stdout-bytes: (string-length stdout)
+        'stderr-bytes: (string-length stderr)
+        'stdout-truncated?: stdout-truncated?
+        'stderr-truncated?: stderr-truncated?
+        'tag: 'worker))
       (make-worker-result
        #t
        status
@@ -202,9 +221,10 @@
        '()
        `((backend . ,(sandbox-backend))
          (capabilities . ,caps)
-         (stdout-truncated? . ,(truncated? stdout stdout*))
-         (stderr-truncated? . ,(truncated? stderr stderr*))
-         (env . ,(env-policy-audit-summary (worker-policy-env-policy pol))))
+         (stdout-truncated? . ,stdout-truncated?)
+         (stderr-truncated? . ,stderr-truncated?)
+         (env . ,(env-policy-audit-summary (worker-policy-env-policy pol)))
+         (audit . ,(audit-log-records log)))
        command)))
 
   (def (worker-run-command* command pol stdin-data)
@@ -227,11 +247,22 @@
              [(eq? (car validation) 'error)
               (worker-refusal-result command pol caps '() (cdr validation))]
              [else
-              (worker-finish-result
-               command
-               pol
-               caps
-               (run-aproc-captured command pol env stdin-data))]))])))
+              (let ([log (audit-log)]
+                    [start-ms (audit-log-now)])
+                (audit-log-record!
+                 log
+                 (audit-process-start
+                  'command: command
+                  'env: (env-policy-audit-summary (worker-policy-env-policy pol))
+                  'cwd: (or (worker-policy-cwd pol) "")
+                  'tag: 'worker))
+                (worker-finish-result
+                 command
+                 pol
+                 caps
+                 (run-aproc-captured command pol env stdin-data)
+                 log
+                 start-ms))]))])))
 
   (def (worker-run-command argv . maybe-policy)
     (let* ([pol (if (null? maybe-policy) (worker-policy) (car maybe-policy))]
diff --git a/tests/test-worker.ss b/tests/test-worker.ss
index 45545a5..be98cce 100644
--- a/tests/test-worker.ss
+++ b/tests/test-worker.ss
@@ -22,6 +22,10 @@
     [(and (pair? al) (assq key al)) => cdr]
     [else default]))
 
+(define (worker-audit-event-types result)
+  (map (lambda (rec) (alist-ref/default rec 'type #f))
+       (alist-ref/default (worker-result-diagnostics result) 'audit '())))
+
 (define (test-pred name expr pred)
   (guard (exn [#t (set! fail (+ fail 1))
                   (printf "FAIL ~a: threw ~a~%" name
@@ -75,6 +79,15 @@
   (worker-run-eval "(+ 1 2)" (test-policy 3000))
   ok-result?)
 
+(test-pred "worker records audit start and exit"
+  (worker-run-eval "(+ 1 2)" (test-policy 3000))
+  (lambda (r)
+    (let ([types (worker-audit-event-types r)])
+      (and (worker-result? r)
+           (worker-result-launched? r)
+           (memq 'process_start types)
+           (memq 'process_exit types)))))
+
 (test-pred "system unavailable in worker"
   (worker-run-eval "(system \"true\")" (test-policy 3000))
   error-result?)
@@ -88,7 +101,7 @@
   error-result?)
 
 (test-pred "cpu spin times out"
-  (worker-run-eval "(let loop () (loop))" (test-policy 200))
+  (worker-run-eval "(let loop () (loop))" (test-policy 50))
   (lambda (r)
     (and (worker-result? r)
          (worker-result-launched? r)