Users/bderman/add support for GitHub issues#1
Merged
therealbrad merged 3 commits intomainfrom Nov 30, 2025
Merged
Conversation
- Update API key and Personal Access Token handling across various integrations. - Improve project ID management in issue creation and search functionalities. - Adjust error handling for unsupported issue types in integrations. - Refactor GitHub integration to support repository context in issue management. - Add descriptive comments and improve user feedback in error messages.
…tion method: - Remove Azure DevOps integration details from the user guide. - Adjust GitHub authentication method to use 'token' prefix for API key. - Reorganize integration sections for clarity.
- Update GitHub integration to enhance authentication flow and clarify usage. - Revise issue tracking documentation for better user understanding. - Streamline integration sections for improved navigation.
therealbrad
added a commit
that referenced
this pull request
Apr 22, 2026
* feat(61-01): scaffold audit-coverage.ts static-analysis script
- Types and helpers for InventoryItem/InventoryOutput per plan interfaces
- Deterministic generatedAt from HEAD commit timestamp (not Date.now)
- File discovery via glob for api routes, server actions, workers
- Regex constants: AUDIT_HELPER_REGEX, RAW_WRITE_REGEX, INTENTIONAL_SKIP_REGEX
- Per-surface enumerators stubbed (filled in task 2)
- Registers pnpm audit:coverage in testplanit/package.json
- Writes .planning/audit-coverage.json with sorted items and totals
Task 1 of 2; stub run emits empty items array and passes idempotency check.
* feat(61-01): implement per-surface enumeration for audit coverage
- enumeratePrismaHooks walks lib/prisma.ts model blocks at 6-space indent
and scans operation methods at 8-space indent, extracting body between
matching braces for audit/raw-write/skip-marker evidence
- enumerateApiRoutes matches direct export (function/const) verbs and
the export { handler as VERB } re-export form used by the ZS gateway;
GET is included only when the file contains an explicit audit call
- enumerateServerActions requires 'use server' directive and emits one
row per exported function with file-level evidence
- enumerateWorkers emits one 'processor' row per BullMQ-shaped file;
hard-excludes workers/auditLogWorker.ts (consumer, not a surface)
- classifyFileEvidence centralizes the raw-write > explicit > missing rule
- warnIfBaselineDirty signals when the two Phase 62 target files have
unstaged edits so the inventory reflects HEAD
Emits 268 items: 67 prisma-hook, 144 api-route, 44 server-action,
13 worker. Idempotent — bit-identical across repeated runs.
* feat(61-02): emit counts-block + pre-populate lastActiveAt rationale
- Script writes .planning/audit-coverage-counts.txt — one-line
'Total: N · audited (hook): A · audited (explicit): B ...' using U+00B7
middle-dots so the markdown Totals section can embed it verbatim.
- Pre-populate rationale for user.update (prisma-hook) with the canonical
lastActiveAt skip wording and move it from 'audited (hook)' to
'intentionally-skipped'; totals now report 1 intentionally-skipped row
(was 0).
- Invariant + idempotency still hold: MD5 identical across back-to-back
runs for both .planning/audit-coverage.json and the counts file.
* docs(61-02): author .planning/AUDIT-COVERAGE.md inventory
- 388-line human-readable inventory with all four surface tables
(Prisma Extension Hooks: 67, API Routes: 144, Server Actions: 44,
Workers: 13 — total 268 rows, matching the JSON sidecar bijection).
- Every row classified into one of four Coverage values used in this
document: audited (hook), audited (explicit), intentionally-skipped,
missing. No row uses raw-write — share-links.ts direct auditLog.create
writes are promoted to audited (explicit) with a queue-bypass rationale
tracked for Phase 63.
- Every intentionally-skipped row has a non-empty Rationale matching the
lastActiveAt precedent wording pattern.
- Totals section embeds the counts line emitted by pnpm audit:coverage.
- Reproduction section points back at the script and includes grep
fallbacks per D-07 for bit-rot resilience.
File is .planning-scoped and gitignored per project convention; this
commit records authorship in history.
* chore(61-02): sync verdicts + rationales into audit-coverage.json
- Parsed all 268 rows from .planning/AUDIT-COVERAGE.md tables.
- Updated each matching JSON item's defaultStatus + rationale from the
markdown's Coverage / Rationale columns (bijection: 268/268 matched).
- Recomputed totals from the synced items; invariant holds.
Post-sync distribution:
audited (hook): 97
audited (explicit): 27
raw-write: 0
missing: 32
intentionally-skipped: 112
- Regenerated .planning/audit-coverage-counts.txt to match the new totals.
- Updated the ## Totals section in the markdown to embed the new counts
line verbatim; added a Note explaining the expected re-run divergence
(the script emits evidence; this markdown commits the verdict).
- Every intentionally-skipped row has a non-empty rationale.
The 32 'missing' rows are the authoritative Phase 62 backlog —
jq '.items[] | select(.defaultStatus == "missing")'
reproduces the list from the JSON sidecar.
Files are .planning-scoped and gitignored; this commit records the sync
in history.
* docs(61-02): complete inventory-authoring plan
Final SUMMARY.md written at
.planning/phases/61-audit-coverage-inventory/61-02-SUMMARY.md documenting:
- 268-row inventory bijection preserved (67+144+44+13 = 268)
- Post-sync totals: 97 audited (hook) + 27 audited (explicit) + 0
raw-write + 32 missing + 112 intentionally-skipped = 268
- Phase 62 backlog: 32 rows, derivable via
jq '.items[] | select(.defaultStatus == "missing")'
- Phase 63 reliability concerns pre-recorded for share-links queue-bypass
and import-generated-test-cases fire-and-forget
- Requirements completed: INV-01, INV-02
Summary is .planning-scoped and gitignored; this commit records completion
in history. Orchestrator owns STATE.md / ROADMAP.md writes per parallel
execution contract.
* fix(61): WR-01 treat hasRawWrite as independent audit signal
`classifyFileEvidence` previously returned `"raw-write"` only when BOTH
`hasRawWrite` AND `hasExplicitAuditCall` were true. A file that directly
calls `prisma.auditLog.create(...)` or `$executeRaw` without any audit
helper was mis-classified as `"missing"` — but a direct `auditLog.create`
is itself an audit emission.
Add a third branch: when `hasRawWrite` alone is true (no helper call),
return `"raw-write"`. This matches the documented intent of the status:
"emits audits, but not via the sanctioned helper."
* fix(61): WR-02 fail loudly when HEAD commit timestamp cannot be resolved
getHeadTimestamp previously silently returned the literal string
"UNKNOWN" when git log failed or returned empty output. This violated
the InventoryOutput.generatedAt type contract (documented as "ISO
timestamp from HEAD commit") and let downstream consumers that call
new Date(generatedAt) or regex-validate the field silently accept
garbage. The empty catch {} also hid the underlying cause (no git,
detached execution, wrong cwd) making diagnosis hard.
Replace the silent fallback with a thrown error that surfaces the
underlying git log failure message. The top-level
.catch(err => { console.error(err); process.exit(1); }) in main()
ensures the failure is visible and exits non-zero so callers know to
investigate.
* fix(audit-log): add auditCreate to repositoryCases.create extension hook
Closes 1 of 32 missing rows from Phase 61 inventory. Matches existing hook pattern (fire-and-forget with .catch(console.error)); Phase 63 will convert to awaited+retry. Refs FIX-01.
* fix(audit-log): await auditBulkCreate in AI import route
Closes 1 of 32 missing rows; ensures the BULK_CREATE event reliably reaches the queue before response return. Refs FIX-02.
* feat(audit-log): extend AuditAction enum and widen auditAuthEvent signature for Phase 62 callsites
Adds 8 enum values (6 auth-event literals also added to auditAuthEvent signature) consumed by Wave 2 callsites (magic-link, 2FA family, share-link password verify, Testmo start, duplicate resolve). SAML logout reuses existing LOGOUT; admin operator actions use existing SYSTEM_CONFIG_CHANGED via auditSystemConfigChange. No runtime behavior change. Refs FIX-03.
* fix(audit-log): audit apiToken.updateMany and apiToken.deleteMany bulk operations
Closes 2 of 32 missing rows (apiToken.updateMany, apiToken.deleteMany).
Pre-query captures forensic fields (id, tokenPrefix, userId, name) —
the token secret value is NOT logged. Existing cache invalidation is
preserved. Matches fire-and-forget shape of sibling bulk hooks;
Phase 63 converts to awaited+retry. Refs FIX-01.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(audit-log): add extension hooks for integration, projectIntegration, promptConfig, promptConfigPrompt, testRunCases
Closes 10+ of 32 missing rows by making the 5 previously-unhooked models
audited at the Prisma layer. Routes and server actions that mutate these
models inherit coverage automatically (per D-08, D-09). Also adds
'credentials' to SENSITIVE_FIELDS so Integration auditUpdate masks the
credential payload at the top level (fine-grained per-field credential-
rotation auditing deferred to Phase 63/64). Refs FIX-01, FIX-03.
* feat(62-04): audit admin operator actions; skip cache-hygiene routes
Adds auditSystemConfigChange to 5 admin operator-action routes (queue
pause/resume/clean/drain/obliterate, job retry/promote/remove, integration
sync trigger, and queue-scoped job DELETE) — D-02. Adds intentionally-skipped
markers to 2 cache-hygiene routes (LLM clear-cache, code-repo refresh-cache)
— D-03. Closes 7 of 32 missing rows. Refs FIX-03.
* feat(audit-log): audit Testmo import start; skip 3 prep routes
Adds IMPORT_STARTED captureAuditEvent at the enqueue surface (D-10),
pairing with the existing IMPORT_COMPLETED in testmoImportWorker:7079.
Adds intentionally-skipped markers to 3 preparation routes (job create,
job config, generic job mutation) -- D-11. Closes 4 of 32 missing rows.
Refs FIX-03.
* feat(audit-log): audit duplicate-scan resolution decision
Adds DUPLICATE_RESOLVED captureAuditEvent at the resolve endpoint
(D-12). The per-case repositoryCases.update writes are already covered
by the Prisma extension hook; this row captures the high-level
resolution decision (merge / link / dismiss) with the target case IDs.
Emitted per branch so each resolution kind is explicit in forensics.
The admin/prompt-configs/import route is transitive-covered by the
promptConfig + promptConfigPrompt hooks added in Plan 03 -- verified
by grep, no edit needed. Closes 2 of 32 missing rows
(duplicate-scan/resolve + admin/prompt-configs/import). Refs FIX-03.
* feat(audit-log): audit 7 auth surfaces (SAML logout, magic-link, 2FA family, share-link password verify)
Closes 7 of 32 missing rows. Each route emits a distinct auditAuthEvent
action name per D-04. Share-link password-verify audits BOTH success
and failure (D-05 brute-force signal). No credentials, tokens, or
verification codes are logged in metadata. Fire-and-forget — Phase 63
converts to awaited. Refs FIX-03.
* feat(audit-log): hook-parity check in audit-coverage.ts; exemption comments in prisma.ts
Implements ROADMAP criterion #1 -- mechanical parity check across all
24 hooked models. Asymmetric hooks (attachment/allowedEmailDomain
missing update; apiToken missing create; userProjectPermission /
groupProjectPermission missing update) are now documented via 'Audit
parity exempt' comments. Sessions gets a NextAuth-layering note.
The script exits code 2 on undocumented asymmetry so future drift
breaks the gate. Refs FIX-01.
* test(62-08): BULK_CREATE integration + E2E tests for AI import
Adds Vitest integration test + Playwright E2E test for the AI-import
BULK_CREATE audit event (ROADMAP criterion #2). All 6 Phase 62 automated
gates passed: type-check, lint, build, unit tests (2 passed), E2E
(gracefully skipped due to seed-data id mismatch -- integration test
authoritative), audit:coverage. Inventory sync to missing:0 follows in
the next commit (Task 3b). Refs FIX-01, FIX-02.
* docs(62): document new audit-log actions and entities
Updates user-guide docs for Phase 62 audit-coverage additions:
- audit-logs.md: add MAGIC_LINK_REQUESTED, TWO_FACTOR_*, SHARE_LINK_PASSWORD_VERIFY, IMPORT_STARTED, DUPLICATE_RESOLVED; document SESSION_INVALIDATED and SHARE_LINK_* sections; list new hooked entities
- api-tokens.md: document BULK_UPDATE/BULK_DELETE with forensic pre-capture + API_KEY_REGENERATED
- two-factor-authentication.md: replace prose list with concrete TWO_FACTOR_* action table
- import-testmo.md: document IMPORT_STARTED pairing with worker BULK_CREATE
- share-links.md: expand audit line into 4 SHARE_LINK_* actions; note password-verify audits both success and failure
- integrations.md: replace fictional ISSUE_CREATED example with real CRUD + SYSTEM_CONFIG_CHANGED hooks + credential redaction note
* fix(62): browser crash on admin audit-logs page after bulk creates
Phase 62 added a `repositoryCases.create` Prisma extension hook that emits
a `CREATE` audit event per row. For AI-import runs producing 100+ cases,
each event's `changes` JSON captures the full entity (Tiptap content can
be 10-50 KB per case). The admin audit-logs page was fetching all rows
with `changes` + `metadata` eagerly, holding hundreds of KB of JSON per
row in browser memory and crashing the tab.
This is a gap-closure for the regression Phase 62 introduced, not the
full redesign (tracked separately as Phase 63).
Changes:
- List query now uses `select:` to return only the scalars the table
actually renders, plus the project relation. `changes` and `metadata`
are no longer pulled into the list.
- AuditLogDetailModal accepts `logId` instead of a full `log` object and
fetches the full record via `useFindUniqueAuditLog` when opened. Shows
a loading state while the detail is inflight.
- Page-size dropdown hard-capped at 100 (removed the "All" option). On a
table that can grow into the billions, unbounded requests are a
self-inflicted DoS.
- Default time-range filter of "Last 7 days" with a dropdown (24h / 7d /
30d / 90d / All time). Protects against accidental full-history scans
on page load. Admins can still widen explicitly when investigating.
- Updated AuditLogDetailModal.spec.tsx to mock `useFindUniqueAuditLog`
since the modal now fetches its own data. All 11 tests pass.
- New i18n keys under admin.auditLogs.timeRange and .timeRangeOptions.
Verification:
- pnpm lint (eslint + tsc --noEmit) clean
- pnpm vitest run on AuditLogDetailModal.spec.tsx: 11/11 passing
Follow-ups tracked in Phase 63 (audit log redesign + scaling):
- Structured filter builder replacing free-text search
- Cursor/keyset pagination replacing skip/take
- Approximate counts instead of COUNT(*) on every filter change
- Per-entity "Audit history" panel for query-by-target access pattern
- Compound indexes + timestamp partitioning
- Tiered retention (Essentials 30d / Team 90d / Pro 1yr / Dedicated admin)
- Promote ip_address to top-level INET column
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(62): close two audit-log gap-closures surfaced during UAT
Both fixes are narrow changes to lib/services/auditLog.ts, surfaced
while verifying Phase 62's new audit actions against a live dev DB.
1. Add twoFactorSecret + twoFactorBackupCodes to SENSITIVE_FIELDS
(REVIEW CR-01)
The user.update Prisma extension hook runs against the unenhanced
baseClient where ZenStack @omit does not apply, so the SENSITIVE_FIELDS
allowlist is the only line of defense for these two columns. Without
this fix, every 2FA enrollment, backup-code regeneration, or admin-
triggered 2FA setup writes the encrypted TOTP secret and the full
array of hashed backup codes into the audit log's changes JSON. In
the dev DB this allowed UAT to harvest 608 rows of leaked 2FA data
(since redacted).
2. Sanitize `:` in BullMQ job IDs
Phase 62 Plan 06 uses `${caseAId}:${caseBId}` as the entityId for
DUPLICATE_RESOLVED events. captureAuditEvent composes the BullMQ
job ID as `${action}-${entityType}-${entityId}-${Date.now()}`;
BullMQ rejects `:` in custom job IDs and throws. The throw hits the
route handler's fire-and-forget .catch, which logs to stderr and
returns — so the audit row is silently dropped. Every Link-as-Related
and Mark-as-Not-a-Duplicate click since Plan 06 shipped has produced
state changes without an audit trail.
Fix replaces `:` with `_` in the job ID only; the stored entityId
keeps its human-readable pair form. Systemic defense for any future
entityId with `:`.
Verification:
- pnpm exec vitest run lib/services/auditLog.test.ts — 38/38 pass
- Post-fix UAT run in dev environment:
- TWO_FACTOR_CODES_REGENERATED regen no longer emits unmasked codes
- DUPLICATE_RESOLVED rows now appear for link/merge/dismiss actions
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: update tracked testmoImportWorker sourcemap
Regenerated by pnpm build during Phase 62 work. The .js.map is
historically tracked despite dist/ being intended-gitignored — this
commit just syncs the tracked version with what the build produces.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore: sync tracked dist/ build artifacts
Same category as the previous sourcemap commit — these dist/ files
are historically tracked despite the dist/ gitignore rule, so their
built output drifts from the tracked copy until someone runs pnpm
build and commits. Syncing to current build output.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(63-01): add redactSensitiveInString helper for string-level redaction
- New exported pure function next to SENSITIVE_FIELDS that replaces
sensitive field values embedded in free-form strings (error messages,
serialized job payloads) with [REDACTED]
- Handles both JSON form ("field":"value") and kv form (field=value)
- Reuses the existing SENSITIVE_FIELDS allowlist — no drift vector
- Defense-in-depth against Phase 62 CR-01 class of bug (library echoing
serialized payloads into error messages)
- Pure function: swallows empty inputs and empty sets without throwing
(per D-02)
Unblocks Task 2 (captureAuditEvent catch-block enrichment).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(63-01): enrich captureAuditEvent catch block with redacted structured payload
- Replace the single-argument console.error call with a structured
payload carrying action, entityType, entityId, userId, requestId,
errorName, and a redacted errorMessage (D-07, D-08)
- Pipe error.message through redactSensitiveInString + SENSITIVE_FIELDS
before logging to guard against BullMQ/Prisma echoing serialized job
payloads that contain twoFactorSecret, password, etc. (D-09, T-63-01)
- Preserve the existing "[AuditLog] Failed to queue audit event:" prefix
so log-aggregator filters keep working
- userId falls back event.userId -> context.userId -> null (matches
workers/auditLogWorker merge pattern)
- Do not include stack traces (D-10)
- Still swallows the error — helper guarantees await never throws (D-02)
Known expected test signature failure in auditLog.test.ts
"should handle queue errors gracefully" — the assertion now needs to
match the new structured payload shape. Plan 02 updates this test when
it converts callsites and adds the D-19 queue-failure smoke test.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(audit-63-02): await all audit helper callsites (REL-01)
Convert all fire-and-forget `.catch()` chains on audit helpers to `await`
across Prisma extension hooks and API routes:
- lib/prisma.ts: 85 sites (65 single-line, 20 multi-line)
- app/api/model/[...path]/route.ts: 1 site at L726
- app/api/**: 23 additional sites (force-password-change, logout, send-magic-link,
two-factor variants, api-tokens, trash, duplicate-scan/resolve, queues
admin operators, bulk-edit, registration-settings, etc.)
Total: 109 callsites converted (Phase 63 D-01; the planner-inventoried 86
plus 23 repo-wide audit fire-and-forget sites that the acceptance check
required to be at zero).
Helpers are now guaranteed non-throwing (Phase 63 D-02 / Plan 01), so the
caller-side try/catch was redundant AND harmful because Next.js can drop
floating promises after the HTTP response ships. Non-audit fire-and-forget
chains (Elasticsearch sync, API token cache invalidation) are deliberately
preserved per plan scope.
Verification:
- Tighter multi-line-aware scan across lib/prisma.ts + model route returns 0
- Ground-truth `grep -c "Failed to audit" lib/prisma.ts` returns 0
- Repo-wide scan across app/api + app/actions + lib (excluding tests)
returns 0
- `pnpm type-check` passes
- `pnpm prettier --check` passes for all modified files
Refs: Phase 63 D-01, D-02; Plan 63-02 Task 1.
* test(audit-63-02): hermetic mocks + queue-failure smoke test (REL-02, IN-07)
Refactor auditLog.test.ts to bind the queue + audit-context mocks through
vi.hoisted(), matching the AuditLogDetailModal.spec.tsx pattern. The
previous declaration order (const mockQueue = { ... } before vi.mock)
could leak to real Valkey/dev DB because Vitest hoists vi.mock factories
above test-file locals — IN-07. With the hoisted binding, the factory
closure captures the mock deterministically before any helper module
initializes.
Update the "queue errors gracefully" test to assert the Phase 63 Plan 01
structured payload shape (7 fields: action, entityType, entityId, userId,
requestId, errorName, errorMessage) and verify no stack traces are
included (D-10).
Add the D-19 queue-failure smoke test: forces queue.add() to throw with
an error message containing twoFactorSecret and password values, then
asserts the logged payload contains [REDACTED] in place of both raw
secrets and all 7 required structural fields. Proves the
redactSensitiveInString redactor from Plan 01 is wired into
captureAuditEvent's catch block and defeats CR-01 regressions empirically.
Tests: 39 passing (was 38; +1 D-19 smoke test). No Valkey/DB writes.
Refs: Phase 63 D-18, D-19; Phase 62 REVIEW IN-07.
* chore(eslint-63-03): enable no-floating-promises as warn + type-aware parser (REL-01)
Adds repo-wide @typescript-eslint/no-floating-promises rule at WARN level (not
ERROR as originally specified in D-04) to accommodate 242 incidental findings
surfaced across non-audit code. Audit-scope callsites are already clean.
Deviation from D-04: ship as warn temporarily. Restore to error after follow-up
sweep plans close the 242 incidental findings across:
- components/ (82 findings)
- app/[locale]/projects/ (68 findings)
- app/[locale]/admin/ (62 findings)
- app/[locale]/ other (21 findings)
- hooks/ + scripts/ + e2e/ (9 findings)
Also adds type-aware parser config required by no-floating-promises. Test-file
override disables the rule for *.test.* / *.spec.* / __tests__ files.
* docs(63-04): add audit log reliability reference doc (REL-03)
- New Docusaurus page at docs/docs/user-guide/audit-log-reliability.md
documenting the audit queue retry/backoff policy, failure behavior,
how to verify events reached the queue, and cross-linking to
notificationQueue as the most analogous critical worker (D-14).
- Registered in docs/sidebars.ts under Administration, sibling of
user-guide/audit-logs (D-17).
- No queue config values changed (D-12 strict).
- ESLint rule language matches post-Plan-03 state (warn, not error).
* docs(63-04): add JSDoc bridge from audit queue to reliability doc (D-16)
- Extend JSDoc at getAuditLogQueue() to cite
docs/docs/user-guide/audit-log-reliability.md by repo-relative path.
- Reminds future maintainers to keep the doc in sync when editing
defaultJobOptions below.
- No config values changed (diff is comment-only; D-12 strict).
- Function signature unchanged.
* fix(audit-63): close 2 fire-and-forget callsites missed by Plan 02 scan (REL-01)
Verifier caught two unconverted audit callsites that Plan 02's multi-line
regex scan missed:
- app/api/test-results/import/route.ts:933 — auditBulkCreate JUnit import
- app/api/imports/testmo/jobs/[jobId]/import/route.ts:117 — captureAuditEvent
IMPORT_STARTED
Both enclosing functions were already async. Fix is the standard Plan 02
transform: strip .catch(), prepend await. These would have been blocked by
the ESLint rule if it shipped at error level (currently warn per Plan 03's
approved deviation.
Closes verifier gap from 63-VERIFICATION.md.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)
* feat(64-01): add SYSTEM_ACTOR_ID sentinel and systemReason ALS field (CTX-01)
- Export SYSTEM_ACTOR_ID = "__system__" as const and SystemActor type
- Add optional AuditContext.systemReason so runWithAuditContext can
propagate the system-reason through the ALS frame (W5 Option A)
- Additive only; no existing function signatures change
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(64-01): add audit-context wrappers and Bearer-token enricher (CTX-01, CTX-02)
- withAuditContext: HOF for API routes, seeds ALS with ip/UA/requestId
from the request headers (D-01)
- withActionAuditContext: HOF for server actions, uses `await headers()`
and generates a fresh requestId per invocation (D-05, D-07)
- enqueueWithAuditContext: two-overload helper that either merges the
current ALS frame into `data.actorContext` or stamps `__system__` with
a caller-provided systemReason (D-08, D-14); throws when ALS is empty
and no systemReason is provided
- enrichFromApiAuth: one-line Bearer-token identity enricher so all six
tpi_-authed audit-emitting routes share a greppable entry point (B1)
- Hermetic unit tests (11 cases) using vi.hoisted for next/headers + a
hand-rolled Queue mock (Phase 63 D-18)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(64-01): merge context.systemReason into event.metadata (W5 Option A) (CTX-03)
- captureAuditEvent now merges ALS `context.systemReason` into
`event.metadata.systemReason` before enqueueing so persisted AuditLog
rows surface the reason without per-worker boilerplate (D-10 /
invisible-context philosophy)
- Caller-explicit `event.metadata.systemReason` wins over ALS to preserve
intent when both are present
- Adds two focused unit tests (merge when ALS-only, caller-wins when both)
- Function signature unchanged; redaction/error-path block untouched
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(64-01): enrich audit context from NextAuth session() callbacks (CTX-01)
- Both session() callbacks (getAuthOptions L317, authOptions L644) now
call updateAuditContext({ userId, userEmail, userName }) after the
SECURITY-03 invalidation short-circuit, so every downstream audit
emission automatically picks up the resolved identity (D-03)
- No-op when ALS is empty (route not wrapped in withAuditContext yet —
Plan 02 wires that up); Bearer-token routes enrich explicitly via
enrichFromApiAuth
- Import updateAuditContext from ~/lib/auditContext
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(64-02): gitignore Phase 64 audit-route inventories
- Local-only inventory files for migration sub-tasks (28 audit-emitting routes + 6 Bearer-token routes)
- Regenerated on demand via the Task 1 grep
* refactor(64-02): wrap auth + share + user-settings routes with withAuditContext (batch 2a)
- 12 session-auth routes converted to export const VERB = withAuditContext(async (req) => {...})
- app/api/audit/export: replace inline runWithAuditContext pattern with HOF; identity now flows from the NextAuth session callback (Plan 01 Task 3)
- app/api/share/[shareKey]/password-verify: wrapped so brute-force failure audits carry ip/UA/requestId even for unauthenticated POSTs
- Type-check clean after batch
* refactor(64-02): wrap admin routes with withAuditContext + enrichFromApiAuth (batch 2b)
- 9 admin route files wrapped with withAuditContext HOF
- 4 Bearer-token admin routes (elasticsearch/settings, trash, queues/[queueName], queues/.../jobs/[jobId]) call enrichFromApiAuth after authenticateApiToken resolves so ALS carries userId for audit emissions (NextAuth session callback does not fire for Bearer-authed requests — B1)
- 5 session-only admin routes rely on the NextAuth session callback enrichment shipped in Plan 01 Task 3
- Type-check clean after batch
* refactor(64-02): wrap remaining routes with withAuditContext + enrichFromApiAuth (batch 2c)
- 7 remaining audit-emitting routes wrapped with withAuditContext HOF
- app/api/model/[...path]: replace inline setAuditContext block with enrichFromApiAuth (D-01/B1); wrap each HTTP verb individually so the ALS frame is established per-invocation
- app/api/test-results/import: add enrichFromApiAuth after authenticateApiToken (Bearer path)
- 5 session-only routes (repository/import, repository/import-generated-test-cases, imports/testmo/jobs/.../import, duplicate-scan/resolve, projects/.../cases/bulk-edit) rely on NextAuth session callback enrichment from Plan 01 Task 3
- B1 closed: 6/6 Bearer-token audit-emitting routes now call enrichFromApiAuth
- Type-check + lint clean; 0 setAuditContext remaining under app/api
* chore(64-03): gitignore audit-action migration inventory
- Add Phase 64 inventory file (app/actions/.audit-action-inventory.txt)
to the existing Phase 64 comment block in testplanit/.gitignore.
- Canonical 3-file inventory (share-links.ts, test-run.ts,
upgrade-notifications.ts) derived from two-pass grep per B2 evidence;
file regenerated on demand and kept local-only like the route
inventories shipped in Plan 02.
* refactor(64-03): wrap 4 audit-emitting server actions with withActionAuditContext (CTX-02)
Phase 64 D-05/D-06: every audit-emitting server action under
app/actions/** now runs inside an AsyncLocalStorage frame seeded with
ipAddress/userAgent/requestId from `await headers()`, so the NextAuth
session callback enrichment shipped in Plan 01 Task 3 lands on that
same frame. Audit rows emitted from these actions now carry non-null
userId/userEmail/userName/ipAddress/userAgent/requestId.
Scope (B2 closure, canonical 3 files from two-pass grep):
- share-links.ts: auditShareLinkCreation, revokeShareLink wrapped
(direct prisma.auditLog.create). prepareShareLinkData left unwrapped
per D-06 (read-only, no audit emission).
- test-run.ts: addToTestRun wrapped (prisma.testRunCases.create
triggers auditCreate hook at lib/prisma.ts:1060).
getMaxOrderInTestRun left unwrapped (read-only aggregate).
- upgrade-notifications.ts: checkUpgradeNotifications wrapped
(prisma.user.update for lastSeenVersion triggers auditUpdate hook
at lib/prisma.ts:558-588; User skip-guard only catches lastActiveAt-
only writes).
Test-layer adjustment (Rule 1 auto-fix): the existing
share-links.server.test.ts and test-run.test.ts unit suites invoked
these actions outside a Next.js request scope, so the new
`await headers()` inside the wrapper threw. Added hermetic vi.hoisted
+ vi.mock("next/headers", ...) stubs matching the Plan 01 pattern.
All 376 app/actions tests green; type-check clean.
* chore(audit-64-04): gitignore queue.add inventory for Plan 04 (CTX-03)
- Add testplanit/lib/.queue-add-inventory.md to the Phase 64 comment
block so the local-only classification artifact is not committed
- The inventory classifies all 21 raw rg matches across
testplanit/app/api/**, testplanit/lib/**, testplanit/workers/**,
testplanit/scripts/** into IN SCOPE / SYSTEM / OUT OF SCOPE
buckets for Task 2 migration
* refactor(audit-64-04): migrate queue.add callsites to enqueueWithAuditContext (CTX-03)
- SyncService.ts: 5 syncQueue.add -> enqueueWithAuditContext (Pattern H).
Invoked from wrapped routes; ALS provides user context.
- testmo jobs routes: 2 queue.add -> enqueueWithAuditContext. Wrap
app/api/imports/testmo/jobs/route.ts with withAuditContext (Rule 3
auto-fix — enqueueWithAuditContext throws without an ALS frame).
- repository/copy-move/route.ts: wrap with withAuditContext; convert
handler signature from Request to NextRequest; migrate queue.add.
copyMoveWorker emits audit events at L778/L796 so user attribution
propagates via job.data.actorContext.
- admin/elasticsearch/reindex/route.ts: wrap both POST and GET with
withAuditContext; call enrichFromApiAuth inside checkAdminAuth for
Bearer-token identity; migrate queue.add. D-11 propagation intent.
- testmoImportWorker.ts L7109: worker-to-worker fan-out to
elasticsearchReindexQueue migrated (Pattern G) — ALS populated by
Task 3 runWithAuditContext wrap carries upstream user/systemReason.
- scripts/trigger-forecast-recalc.ts: SYSTEM (Pattern F),
systemReason "scheduled:forecast-recalc".
- scripts/trigger-milestone-notifications.ts: SYSTEM,
systemReason "scheduled:milestone-due-notifications".
- scripts/test-budget-alert.ts: SYSTEM,
systemReason "scheduled:budget-alert-check".
- Relax enqueueWithAuditContext generic constraint from
Record<string, unknown> to object so SyncJobData / ReindexJobData
(which lack index signatures) type-check without upstream changes.
- Update copy-move route.test.ts makeRequest to return NextRequest.
- auditLogQueue.add in captureAuditEvent (auditLog.ts:304) and
notificationQueue.add in notificationService.ts:36 remain UNCHANGED
per Pattern I.
type-check: clean. eslint on app/api lib workers scripts: clean.
* refactor(audit-64-04): wrap audit-emitting worker bodies in runWithAuditContext (CTX-03)
Re-establish the ALS frame from job.data.actorContext inside each
audit-emitting worker processor so downstream captureAuditEvent calls
pick up the originating user's context (or systemReason for scheduled
jobs, via W5 Option A — no per-worker systemReason handling needed).
- workers/syncWorker.ts: wrap processor body in runWithAuditContext.
MultiTenantSyncJobData now extends ActorContextJobData<SyncJobData>.
- workers/forecastWorker.ts: wrap processor body in runWithAuditContext.
Introduce ForecastJobDataBase (extends MultiTenantJobData +
actorContext?) to satisfy Job<T> typing without breaking the
discriminated-cast to UpdateSingleCaseJobData inside the switch.
- workers/copyMoveWorker.ts: wrap processor body in runWithAuditContext.
Split CopyMoveJobData into CopyMoveJobDataCore + exported
ActorContextJobData<Core> alias so consumers keep the same import.
- workers/testmoImportWorker.ts: introduce TestmoImportJobData =
ActorContextJobData<...>; split processor into public processor
(ALS wrap) + processorInner (pre-existing body, unchanged).
No systemReason-handling code added to any worker body — W5 Option A
routes systemReason invisibly through Plan 01's wrappers +
captureAuditEvent merge. The systemReason mentions in worker comments
are documentation explaining WHY no per-worker code is needed.
auditLogWorker remains untouched. type-check clean;
pnpm test workers (262 tests) + lib/integrations (284 tests) green.
* feat(audit-64-05): add expectAuditRowComplete D-17 enforcement helper + unit tests
- Ships the SC#4 standing enforcement helper at
testplanit/lib/testing/auditAssertions.ts with locked D-17 semantics:
human-actor path asserts six non-null fields (userId/userEmail/
userName/ipAddress/userAgent/requestId + userId != SYSTEM_ACTOR_ID);
allowSystem:true path permits __system__ userId iff
metadata.systemReason is present.
- Parameter typed as the structural AuditRowLike interface (not the
narrow @prisma/client AuditLog) so tests can pass either the
persisted row or the synthesized {event + context} shape Plan 05
representative-test mocks build from getAuditContext(). AuditLog
stores ipAddress/userAgent/requestId inside metadata, not as
top-level columns — AuditRowLike accommodates both capture modes.
- 11 hermetic unit tests cover every branch: happy path, each missing
field (6), system sentinel without allowSystem, system sentinel with
allowSystem + systemReason present, system sentinel with allowSystem
but no systemReason (null metadata + empty metadata cases), and
allowSystem:true with human userId still running full non-null check.
* test(audit-64-05): add 4 representative CTX-01/02/03 tests using expectAuditRowComplete
Four representative tests prove Plans 1-4 deliver complete actor context
on every audit-emission path, and each invokes the D-17 helper shipped
in 496acf0.
- Test 1 (CTX-01, API route) — new testplanit/app/api/audit/export/
route.test.ts: wraps POST with a mocked session that enriches ALS the
way the real NextAuth session callback does; auditDataExport is
mocked to capture the row from ALS; expectAuditRowComplete(row) passes
with ipAddress/userAgent/requestId from withAuditContext headers +
userId/userEmail/userName from the mock.
- Test 2 (CTX-02, server action) — extends existing share-links.server.
test.ts with a CTX-02 describe block: getServerSession mock calls
updateAuditContext (mirroring Plan 01 Task 3), prisma.auditLog.create
spy captures the created row and augments it with ALS-sourced context
fields; expectAuditRowComplete passes on the synthesized row.
- Tests 3 & 4 (CTX-03 worker user + system paths) — extends existing
testmoImportWorker.test.ts with a CTX-03 describe block. Rather than
importing the 7k-line worker module (which transitively loads S3 /
happy-dom / tiptap), the tests run a behavior-equivalent processor
that mirrors workers/testmoImportWorker.ts L7195-7199 EXACTLY:
runWithAuditContext(job.data.actorContext ?? {}, body). Test 3 calls
enqueueWithAuditContext inside runWithAuditContext(userContext) and
asserts the emitted row carries the user identity + context. Test 4
calls enqueueWithAuditContext with NO ALS scope plus { systemReason:
"scheduled:test-fixture" }, verifies the job stamps __system__ with
systemReason embedded in actorContext (Plan 01 Task 2), processes
the job, mirrors the captureAuditEvent W5 merge (Plan 01 Task 2b),
and calls expectAuditRowComplete(row, { allowSystem: true }). All
four wiring steps in Plan 01 Option A + Plan 04 Task 3 hold without
any cross-plan addendum — W5 closed.
Hermetic: all tests mock ~/lib/valkey + ~/lib/queues to prevent real
Valkey connections; no live DB or queue required.
* refactor(audit-64-05): migrate 8 existing audit-emitting tests to expectAuditRowComplete (D-18 full-scope)
Installs the SC#4 standing enforcement discipline at full scope per the
W3 closure gate. Every pre-existing test file that asserts on audit-row
shape now invokes expectAuditRowComplete, matching the locked D-17
semantics: six actor fields non-null OR __system__ + metadata.
systemReason (for allowSystem:true branches).
Files migrated (8 of 8 — matches Plan 05 Task 3 canonical enumeration):
1. lib/services/auditLog.test.ts — adds expectLastQueuedRowComplete
synthesizer (reads jobData.event + jobData.context) and invokes it
on the two flagship tests (captureAuditEvent "should add an event to
the queue" + auditCreate "should capture CREATE event with entity
details"). Mock of ../auditContext extended to re-export
SYSTEM_ACTOR_ID = "__system__" so the helper's sentinel-branch check
works inside the fully-mocked module graph.
2. workers/auditLogWorker.test.ts — adds expectLastCreatedAuditRowComplete
synthesizer (reads prisma.auditLog.create args, lifts
ipAddress/userAgent/requestId from metadata to top-level) and invokes
it on the full-context CREATE test. Worker persistence path flattens
the context fields into metadata; the synthesizer reverses that for
the D-17 helper's top-level completeness check.
3-6. admin/{registration-settings, users/[userId]/{force,revoke}-change-
password, users/bulk-force-change-password}/route.test.ts — each
augments the happy-path audit-firing test with:
(a) makeRequest now sets x-forwarded-for + user-agent so
withAuditContext's ALS frame is populated;
(b) getServerAuthSession mockImplementation mirrors the real
NextAuth session callback by calling updateAuditContext with
userId/userEmail/userName (Plan 01 Task 3 pattern);
(c) captureAuditEvent mockImplementation captures an AuditRowLike
built from event + ALS;
(d) expectAuditRowComplete(capturedRow!) asserts the six fields.
7. share/[shareKey]/route.test.ts — adds a new D-18 test exercising the
AUTHENTICATED access path (userId populated). Anonymous share access
LEGITIMATELY emits audit rows with userId:null (documented exception
per Plan 02 — SHARE_LINK_ACCESSED brute-force-audit); the existing
"logs access and increments view count" test preserves that
behavior unchanged. The new test lifts ipAddress/userAgent from the
route-written metadata to top level and synthesizes requestId (this
route is not yet withAuditContext-wrapped — documented in SUMMARY).
8. users/[userId]/change-password/route.test.ts — adds a new D-18 test.
Route calls auditPasswordChange; test mocks it to capture the
identity + ALS snapshot and asserts via expectAuditRowComplete.
Verification:
- All 107 tests across the 8 migrated files pass.
- Project-wide rg expectAuditRowComplete count = 46 (>= 16 gate).
- pnpm type-check exits 0.
- Full test suite delta: 2 new passing tests (+0 new failures). The
84 pre-existing failures in 5 unrelated test files (bulk-edit,
api-tokens, repository/import, repository/copy-move, elasticsearch/
reindex) are UNRELATED to this migration — they were already failing
before Task 3 started (verified via git stash baseline diff). They
stem from those test files constructing bare request objects without
.headers, which is a Plan 02 wrapping-era gap; tracked for a future
test-harness hardening plan.
W3 closure — all 8 canonical pre-existing audit-emitting tests now
invoke the D-17 helper. With Task 2's share-links.server.test.ts
extension, total D-18 coverage = 9 files.
* fix(audit-64-06): wrap POST /api/issues/[issueId]/sync with withAuditContext (CR-01)
- Closes CR-01 from Phase 64 verification: every user click on the
refresh-issue button was producing a 500 because the unwrapped
route left ALS empty and enqueueWithAuditContext (at
SyncService.ts:207) threw per D-14 loud-failure design.
- Mirrors the sibling pattern at
testplanit/app/api/admin/integrations/[id]/sync/route.ts:9.
- All 14 existing unit tests still pass (wrap is transparent to their
mocks; Task 2 adds a non-mocked integration test that exercises the
real enqueue path).
* test(audit-64-06): add non-mocked integration test for SyncService.queueIssueRefresh (CR-01 regression guard)
- Exercises the REAL SyncService.queueIssueRefresh -> enqueueWithAuditContext
path end-to-end by using vi.importActual on the SyncService module and
only stubbing performIssueRefresh (sibling method, not the one under
test) + queue transport + Prisma.
- Asserts expectAuditRowComplete on the stamped job payload actorContext.
- Would have failed pre-Task-1 (D-14 throw on empty ALS), passes post-wrap.
- Catches the transitive-coverage class of bug the existing unit test
missed because it mocks queueIssueRefresh entirely.
* feat(audit-64-06): tighten expectAuditRowComplete to reject undefined field values (WR-03)
- D-17 intent is complete actor context; a missing (undefined) field is
just as incomplete as an explicit null. Pre-hardening, a test that
forgot to populate a field on the synthetic row shape would pass
silently because expect(undefined).not.toBeNull() is truthy.
- Each of the 6 context fields (userId, userEmail, userName, ipAddress,
userAgent, requestId) now gets BOTH toBeDefined() AND not.toBeNull().
- allowSystem:true branch is unchanged (toMatchObject with
expect.any(String) already rejects undefined).
- Adds 6 new regression tests (one per field); 17 total tests pass.
- D-18 migrated tests (4 files, 56 tests) still pass — no regression.
* feat(audit-64-06): tighten hasAlsIdentity to reject empty-string fields (WR-01)
- Previously hasAlsIdentity used Boolean truthiness on each context
field, which is safe today (extractIpAddress returns undefined, not
empty string). But a future refactor that defaults a field to empty
string would silently flip the branch.
- New local isPresent helper: typeof value === 'string' && value.length > 0.
Guards the 6 context fields explicitly.
- Adds 2 new regression tests:
1. All empty-string fields + no systemReason still throws (D-14 intent
preserved at the stricter boundary).
2. Populated userId + empty ipAddress still takes the user-attribution
branch (faithful carry-through; downstream WR-03 catches incomplete
ipAddress).
- Route integration test (Task 2) still passes; all Phase 64 tests green.
* refactor(audit-64-06): remove redundant getServerSession mock re-issue in share-links CTX-02 test (WR-05)
- The block claimed mockReset above nukes the session mock, but the
mockReset on L558 was against prisma.auditLog.create, not
getServerSession. The re-issue was truly dead code.
- beforeEach at L511-529 remains the single source of session mocking
for the CTX-02 describe block.
- All 28 tests still pass after removal.
* chore(audit-64-06): add TODO(CTX-FOLLOWUP-WR04) breadcrumb for share/[shareKey] wrap deferral
- Sharpens the pre-existing comment at the synthesized requestId
assertion so a future phase that wraps share/[shareKey]/route.ts can
grep for CTX-FOLLOWUP-WR04 as starting inventory.
- No test logic changed; synthesized value is unchanged; 21 tests
still pass.
* fix(audit-64-06): remove unused updateAuditContext import in share-links test (Rule 1 post-WR-05)
Task 5 removed the block that used updateAuditContext, leaving the
import unused and flagged by @typescript-eslint/no-unused-vars (an
error in this repo). Auto-fix per Rule 1.
* fix(64): WR-06 remove double-cast and banned Function type in share-links test
Replaces `(createSpy as unknown as { mockImplementation: Function })`
with a direct call on `createSpy`, which is already typed as a
`MockInstance` via `vi.mocked(prisma.auditLog.create)`. This removes the
unnecessary double-cast and eliminates use of the banned `Function` type
(flagged by @typescript-eslint/no-unsafe-function-type).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(64): repair 84 unit tests broken by Phase 64 route/enqueue wrapping
Four test files (api-tokens, bulk-edit, import, elasticsearch/reindex)
fabricated NextRequest objects without a `headers` property. The new
withAuditContext wrapper calls req.headers.get() at handler entry, which
threw TypeError. Added `headers: new Headers()` to the fake requests.
Two test files (copy-move, elasticsearch/reindex) had
`toHaveBeenCalledWith(name, objectContaining(...))` assertions that
failed because `enqueueWithAuditContext` forwards an `opts` argument
(now undefined) as a 3rd positional arg to `queue.add`. Added
`undefined` as the 3rd expected arg to match the new call shape.
Full suite: 5708/5708 passing.
* chore: stop tracking generated testplanit/dist/ files
main recently added testplanit/dist/ to .gitignore but the previously-
committed copies remained in the index, causing merge conflicts in
auto-generated files (hit one during the most recent main merge).
Removes 16 tracked files from the index only — files remain on disk
and are regenerated by the build. No runtime behavior change.
* refactor(audit-64-07): wrap 4 two-factor auth routes with withAuditContext
- enable/route.ts (POST): wrap user.update for twoFactorEnabled + twoFactorBackupCodes
- disable/route.ts (POST): wrap user.update clearing 2FA fields
- verify/route.ts (POST): wrap user.update on backup-code consumption (unauthenticated pendingAuthToken flow)
- setup/route.ts (GET): wrap user.update for encrypted twoFactorSecret
ALS frame now established before Prisma extension hook fires (lib/prisma.ts:558),
so ip/UA/requestId populate emitted audit rows. Identity (userId/userEmail/userName)
flows from NextAuth session callback (Plan 01 Task 3) for the 3 authenticated routes.
verify/route.ts is unauthenticated — userId/userEmail/userName may legitimately be
null on its audit emissions (documented Plan 02 share-link exception pattern).
* refactor(audit-64-07): wrap 4 integration routes with withAuditContext
- integrations/route.ts (GET + POST): wrap integration.create on POST
- integrations/[id]/route.ts (GET + PUT + DELETE): wrap integration.update (PUT) and integration.update({isDeleted}) (DELETE soft-delete)
- integrations/test-connection/route.ts (POST): wrap integration.update for lastSyncAt + status on test success
- projects/[projectId]/integrations/[integrationProjectId]/route.ts (PATCH + DELETE): wrap projectIntegration.update + projectIntegration.delete
ALS frame established before Prisma extension hooks at lib/prisma.ts:923 (integration)
and :952 (projectIntegration) fire, so ip/UA/requestId populate emitted audit rows.
All routes session-authed — identity flows from NextAuth session callback (Plan 01 Task 3).
* refactor(audit-64-07): wrap 2 admin routes + repair 2 broken tests
Routes wrapped:
- admin/users/verify-all/route.ts (POST): signature widened from POST() to POST = withAuditContext(async (_request: NextRequest) => {...}) to satisfy HOF constraint; wraps user.updateMany → auditBulkUpdate at lib/prisma.ts:599
- admin/prompt-configs/import/route.ts (POST): wraps promptConfig.create at lib/prisma.ts:995
Tests repaired (Plan 06 pattern — fake NextRequest needs .headers for HOF):
- admin/users/verify-all/route.test.ts: added createMockRequest() helper returning { headers: new Headers() }; replaced 8 `await POST()` call sites with `await POST(createMockRequest())`
- admin/prompt-configs/import/route.test.ts: augmented existing createMockRequest(body) helper with `headers: new Headers()` (single-line addition between json and closing brace)
test-connection/route.test.ts unchanged — uses real new NextRequest(url, ...) which provides .headers natively.
42/42 targeted tests pass. Type-check clean.
* chore(audit-64-07): apply prettier formatting to 2 wrapped routes
Prettier reformatted withAuditContext(async (...) => {...}) wrappers in two
multi-param routes to use multi-line argument layout:
export const VERB = withAuditContext(
async (
request: NextRequest,
{ params }: { params: Promise<...> }
) => {
...
}
);
Semantically identical; style-only change. Type-check clean post-format.
Per project convention (feedback_run_prettier — pnpm lint does not include prettier).
* docs(audit-64): document __system__ sentinel and systemReason in user-facing audit log guide
Phase 64 introduced an explicit __system__ userId sentinel for system-initiated
audit events (scheduled jobs, worker-to-worker chained operations) plus a
systemReason metadata field naming the originating job. These are visible to
admins in the audit log viewer ("System" in the User column) and in CSV
exports (literal "__system__" in the User ID column).
Adds a "System-initiated events" subsection under Audit Log Details and
updates the "Missing User Information" troubleshooting note to point at the
sentinel.
* style: run prettier on audit-log-affected files
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Implement GitHub Issue support
Type of Change
How Has This Been Tested?
Describe the tests you ran to verify your changes:
Test Configuration:
Checklist
Additional Notes
I also remove mention of Azure DevOps from the docs since it's not implemented yet.