Commit Graph

395 Commits

Author SHA1 Message Date
jared 6b2d8e4d03 Fix remaining spam issues and phpcs merge conflict marker
Lint / PHP (phpcs PSR-12) (push) Successful in 31s
Lint / JS (eslint) (push) Successful in 13s
Security / PHP Security (semgrep) (push) Failing after 1m41s
Lint / Deploy (push) Successful in 4s
Lint / Notify on failure (push) Has been skipped
Spam fixes:
- Add ZFS pool category to hash with subtypes (pool_state, pool_usage,
  pool_errors) so DEGRADED and usage-high on same pool get separate tickets
- Strip volatile percentages from LXC/ZFS usage titles ("usage high: 80.1%"
  → "usage high") and OSD counts from BlueStore slow-ops titles
  ("2 OSD(s) experiencing" → "OSD(s) experiencing") in hwmonDaemon.py

phpcs fix:
- Remove leftover merge conflict marker (<<<<<<< HEAD / >>>>>>>)
  in create_ticket_api.php which caused phpcs to fail on bitshift
  operator spacing

DB cleanup:
- Deleted 107 spam comments and 107 audit entries from tickets
  357934698 (ZFS pool), 673679581 (BlueStore), 925498317 (LXC storage)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
deploy-2026.04.16-23
2026-04-16 08:28:59 -04:00
jared 7fb60a365e Suppress title-only update comments to stop hourly comment spam
Lint / PHP (phpcs PSR-12) (push) Failing after 45s
Lint / JS (eslint) (push) Successful in 19s
Security / PHP Security (semgrep) (push) Failing after 1m37s
Lint / Deploy (push) Has been skipped
Lint / Notify on failure (push) Successful in 2s
Comments on worsening condition now only fire on priority escalation.
Title and description updates are silent — title changes (e.g. rising
Power_On_Hours counters) were generating a comment on every hourly run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 08:16:41 -04:00
jared fb3b607bd1 Resolve merge conflict in create_ticket_api.php OSD regex
Lint / PHP (phpcs PSR-12) (push) Failing after 33s
Lint / JS (eslint) (push) Successful in 15s
Security / PHP Security (semgrep) (push) Failing after 2m20s
Lint / Deploy (push) Has been skipped
Lint / Notify on failure (push) Has been cancelled
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 08:10:14 -04:00
jared dad7c24bff Fix hwmonDaemon hash collisions and automated comment formatting
- source_type (auto vs manual) added to dedup hash so automated
  tickets never collide with manually created ones
- OSD-specific subtype (osd_down_N) so each OSD gets its own ticket
- Description refreshed on every automated update (current sensor data)
- Comments on worsening condition only fire on meaningful changes
- ASCII art descriptions wrapped in fenced code blocks in comments
- Reopen comment also uses fenced code block

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 08:09:12 -04:00
jared 9e9d8a33e3 Fix hwmonDaemon hash collisions and automated description rendering
Lint / PHP (phpcs PSR-12) (push) Successful in 40s
Lint / JS (eslint) (push) Successful in 13s
Security / PHP Security (semgrep) (push) Failing after 1m59s
Lint / Deploy (push) Successful in 5s
Lint / Notify on failure (push) Has been skipped
- Include source_type (auto vs manual) in dedup hash so automated
  tickets never collide with manually created ones. This was causing
  hwmonDaemon to hijack manual task tickets that shared the same
  cluster/category/environment tags.

- Include specific OSD ID in hash subtype (osd_down_N) so each OSD
  failure gets its own ticket instead of all colliding to osd_down.

- Wrap hwmonDaemon report descriptions in fenced code blocks in
  comments so ASCII art box-drawing renders correctly instead of
  collapsing into a paragraph blob.

- Refresh ticket description on every automated update so the ticket
  body shows current sensor data, not stale values from first report.

- Only post a worsening-condition comment when title or priority
  actually changed (not just a description refresh).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
deploy-2026.04.16-11
2026-04-16 08:06:13 -04:00
jared dfae1d4648 ci: add notify-failure, deploy tagging, and PHP security scanning
Lint / PHP (phpcs PSR-12) (push) Successful in 26s
Lint / JS (eslint) (push) Successful in 12s
Security / PHP Security (semgrep) (push) Failing after 55s
Lint / Deploy (push) Successful in 3s
Lint / Notify on failure (push) Has been skipped
- lint.yml: add notify-failure Matrix alert job; add Tag deployed commit
  step (main branch only) with deploy-YYYY.MM.DD-N tagging via Gitea API;
  add permissions: contents: write to deploy job
- security.yml: new workflow running semgrep with p/php and p/owasp-top-ten
  configs on push, PR, and weekly schedule

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
deploy-2026.04.14-9
2026-04-14 16:25:18 -04:00
jared ac82300675 Add CI badge and CI/CD section to README
Lint / PHP (phpcs PSR-12) (push) Successful in 27s
Lint / JS (eslint) (push) Successful in 16m21s
Lint / Deploy (push) Successful in 4s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:54:00 -04:00
jared 31510cfe0f ci: gate deploy behind lint — Actions triggers webhook after lint passes
Lint / PHP (phpcs PSR-12) (push) Successful in 30s
Lint / JS (eslint) (push) Successful in 13s
Lint / Deploy (push) Successful in 3s
Adds a deploy job that runs only when both php-lint and js-lint succeed.
Calls the CT132 webhook directly with HMAC-SHA256 signature from the
WEBHOOK_SECRET repo secret. Disabled the direct push webhooks that
previously deployed on every push regardless of lint status.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 21:42:34 -04:00
jared 5fce716489 style: fix final 2 phpcs violations; exclude line-length rule
Lint / PHP (phpcs PSR-12) (push) Successful in 36s
Lint / JS (eslint) (push) Successful in 16s
2026-04-13 21:08:19 -04:00
jared c90bdc8ac8 style: auto-fix 1340 phpcs PSR-12 violations via phpcbf; exclude MissingNamespace and SideEffects
Lint / PHP (phpcs PSR-12) (push) Failing after 29s
Lint / JS (eslint) (push) Successful in 12s
2026-04-13 20:56:10 -04:00
jared b6df647921 ci: add php-xml for phpcs xmlwriter/SimpleXML deps
Lint / PHP (phpcs PSR-12) (push) Failing after 28s
Lint / JS (eslint) (push) Successful in 12s
2026-04-13 20:51:17 -04:00
jared e3a115fd02 ci: install php via apt, relax eslint rules for existing codebase
Lint / PHP (phpcs PSR-12) (push) Failing after 26s
Lint / JS (eslint) (push) Successful in 12s
2026-04-13 20:47:26 -04:00
jared 46285b8abc ci: use php:8.2-cli container for phpcs job
Lint / PHP (phpcs PSR-12) (push) Failing after 19s
Lint / JS (eslint) (push) Failing after 13s
2026-04-13 20:41:03 -04:00
jared d38cc1bfbe ci: add phpcs and eslint linting workflow
Lint / PHP (phpcs PSR-12) (push) Failing after 7s
Lint / JS (eslint) (push) Failing after 16s
2026-04-13 20:34:21 -04:00
jared 56007f7479 Fix admin dropdown dismissing when cursor moves into menu
The menu was positioned top:calc(100%+4px), leaving a 4px dead zone
between the trigger and the menu that interrupted the :hover chain.
Changed to top:100% with padding-top:6px + margin-top:-2px so the
menu's hover area is contiguous with the trigger — no more needing
to mouse over quickly to keep the dropdown open.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 00:41:29 -04:00
jared 7dba849c12 Fix footer appearing mid-page when content is minimal
body lacked display:flex + flex-direction:column, so the existing
flex:1 on .lt-main had no effect. Error pages (404, 403) and any
page with little content showed the footer immediately after content
rather than pinned to the viewport bottom.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 00:37:28 -04:00
jared 3e9f5e82db Use styled 404 page for missing/inaccessible tickets
Replaces the bare "Ticket not found" text response with the shared
views/error_404.php partial so users see the full TDS-styled error page.
Also collapsed the two identical 404 branches into one check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 00:34:15 -04:00
jared f42ee8070f Fix COPY button on API Keys page
lt.copy() does not exist — the correct API is lt.clipboard.copy().
Also added ok-check since clipboardCopy() returns a boolean promise,
not a rejection on failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 00:03:37 -04:00
jared 3b0b7621e0 Block web access to generate_api_key.php
Added php_sapi_name() CLI guard matching the pattern used in migrate.php
and cleanup_ratelimit.php. Without this, the script was web-accessible
and could generate an API key without authentication if no keys existed yet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 21:44:35 -04:00
jared e3ebc766e5 Fix open redirect in legacy ticket.php URL handler
The /ticket.php?id=VALUE redirect did not validate the id parameter,
allowing path traversal (e.g. ?id=../admin) or other unexpected values
in the Location header. Added ctype_digit validation so only positive
numeric IDs are redirected to /ticket/N; anything else falls back to /.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 21:08:55 -04:00
jared 2d6b2b8058 Fix watcher self-notification, unescaped output in views
- NotificationHelper::notifyWatchers: excludeUserId parameter was
  accepted but never used; actors were notified of their own actions.
  Fix: add AND tw.user_id != ? clause to watcher query when exclusion
  is requested.

- TicketView.php: formatAction() default case returned raw
  $event['action_type'] unescaped into HTML context. Fix: wrap with
  htmlspecialchars().

- Admin views: field_id, recurring_id, template_id, transition_id
  in data-id attributes were uncast; field_type was unescaped in
  CustomFieldsView; from/to_status slugs derived from DB values were
  used directly in class attributes in WorkflowDesignerView.
  Fix: (int) cast for IDs, htmlspecialchars for field_type,
  preg_replace to sanitize DB-derived CSS class slugs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 20:41:09 -04:00
jared 3c7b3475e4 Fix command palette 'Unassigned Tickets' filter using wrong parameter
The filter action used ?assigned_to=none but DashboardController only
recognises 'unassigned' as the sentinel value for that filter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 14:30:31 -04:00
jared 55c2d5c596 Fix visibility bypass in export and insecure cookie in preferences
api/export_tickets.php: getAllTickets() was called without $currentUser,
so visibility filtering was skipped — any authenticated user could export
all tickets including confidential/internal ones.

api/user_preferences.php: the single-preference setcookie() call was
missing httponly/secure flags (batch path had them correctly). Also cast
preference values to string before passing to setPreference(string).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 14:29:09 -04:00
jared 0f71ef9935 Fix ?priority=N filter from command palette not being applied
The command palette 'P1 Critical Tickets' link uses ?priority=1, but
getAllTickets only accepted priority_min/priority_max. Resolve the single
?priority=N param to both priority_min and priority_max so it acts as
an exact priority match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 14:20:48 -04:00
jared e2eabad413 Fix assigned_to=me filter not working from command palette
The command palette 'My Open Tickets' action navigates to
?assigned_to=me, but DashboardController only handled numeric IDs
and 'unassigned', silently ignoring 'me'. Resolve 'me' to the
current user's ID. Also update the active filter chip to display
'Me' instead of 'User #me'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 14:19:11 -04:00
jared 9a8940b9d0 Fix createTicket duplicate-key retry handler for PHP 8.2
PHP 8.2 strict mysqli mode throws mysqli_sql_exception on duplicate key
rather than returning false from execute(). Replace the old if/else errno
check with try/catch on mysqli_sql_exception, re-throw non-1062 errors,
and use random_int range 100000000-999999999 (no leading zeros) for the
retry ID.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 14:17:34 -04:00
jared f7321863e6 Merge remote-tracking branch 'origin/main' into development 2026-04-11 14:11:16 -04:00
jared d21691a548 Fix deleteTicket crash when ticket_custom_fields table doesn't exist
PHP 8.2 raises mysqli_sql_exception on prepare() for non-existent tables
rather than returning false. Wrap each child-table delete in try/catch and
silently skip tables that don't exist in all deployments, re-throwing for
unexpected errors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 14:09:34 -04:00
jared b385e177ec Merge branch 'development' 2026-04-11 13:45:47 -04:00
jared 60f23051a9 Fix notifications not detecting comment events
AuditLogModel::logCommentCreate logs comments with action_type='comment'
not 'create'. The notification query was filtering on action_type='create'
only, so comment events on watched/owned tickets were never surfaced.

Widen the filter to IN ('comment', 'create') to match the actual logged
values while staying compatible with any legacy entries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 13:45:04 -04:00
jared f9faca55bb Merge branch 'development' 2026-04-11 13:42:49 -04:00
jared 1b75ad14fb Fix kanban drag-and-drop to send ticket_id as string
parseInt(ticketId, 10) was stripping leading zeros before sending
to update_ticket.php. Switch to String(ticketId) for consistency
with all other ticket ID handling in the JS codebase.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 13:42:30 -04:00
jared 1a85d20b8e Merge branch 'development' 2026-04-11 13:41:00 -04:00
jared c442e2d47f Fix AttachmentModel ticket_id binding to preserve leading zeros
All ticket_id parameters were bound as integer ("i"), which stripped
leading zeros before insertion into ticket_attachments.ticket_id
(VARCHAR 9). This caused a mismatch: upload_attachment.php creates
the directory using the full string (e.g. /uploads/000123456/) but
the DB stored the integer form ("123456"), so download and delete
would look in the wrong path.

Changed getAttachments, addAttachment, getTotalSizeForTicket, and
getAttachmentCount to use string binding ("s") so the canonical
zero-padded ticket ID is stored and read back consistently.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 13:40:15 -04:00
jared a62123236d Merge branch 'development' 2026-04-11 13:31:34 -04:00
jared 47b70b0ee8 Fix ticket ID handling in assign and delete_attachment APIs
assign_ticket.php: preserve string ticket ID (ctype_digit validation)
  instead of (int) cast for consistent audit logging and URL generation.

delete_attachment.php: use string ticket_id from DB for the upload
  directory path — (int) cast was stripping leading zeros, causing
  the wrong path (/uploads/123456/) instead of /uploads/000123456/.
  Also pass raw string to getTicketById() to let TicketModel handle
  type coercion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 13:31:10 -04:00
jared b841037130 Merge branch 'development' 2026-04-11 13:22:20 -04:00
jared 6b89a14a47 Fix ticket ID generation in create_ticket_api.php to avoid leading zeros
Use random_int(100000000-999999999) so IDs are always 9 digits without
a leading zero, matching the behaviour of TicketModel::createTicket().
The old sprintf('%09d', mt_rand(1, ...)) could produce IDs like
000123456 which broke PHP array key lookups elsewhere.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 13:06:08 -04:00
jared 63092ac070 Fix leading-zero ticket ID in clone_ticket.php
- Preserve source ticket ID as string (ctype_digit validation)
  instead of int-casting with (int)
- Use $sourceTicket['ticket_id'] (canonical DB form) when creating
  the relates_to dependency and audit log entry; avoids storing
  "123456" instead of "000123456" which breaks string-based
  depends_on_id lookups in DependencyModel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 13:03:16 -04:00
jared d0c889a594 Fix leading-zero ticket ID in clone_ticket.php
- Preserve source ticket ID as string (ctype_digit validation)
  instead of int-casting with (int)
- Use $sourceTicket['ticket_id'] (canonical DB form) when creating
  the relates_to dependency and audit log entry; avoids storing
  "123456" instead of "000123456" which breaks string-based
  depends_on_id lookups in DependencyModel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 13:02:49 -04:00
jared f93cebe2d9 Implement bulk_delete operation; validate operation types
- TicketModel: add deleteTicket() that removes all child records
  (comments, watchers, dependencies, attachments, custom fields)
  then deletes the ticket and cleans up physical attachment files
- BulkOperationsModel: add bulk_delete case to processBulkOperation()
  so the "Bulk Delete" UI button actually works instead of silently
  failing with N failures
- bulk_operation.php: validate operation_type against whitelist to
  reject unknown operations early with a proper error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 12:54:35 -04:00
jared ab0edd1325 Implement bulk_delete operation; validate operation types
- TicketModel: add deleteTicket() that removes all child records
  (comments, watchers, dependencies, attachments, custom fields)
  then deletes the ticket and cleans up physical attachment files
- BulkOperationsModel: add bulk_delete case to processBulkOperation()
  so the "Bulk Delete" UI button actually works instead of silently
  failing with N failures
- bulk_operation.php: validate operation_type against whitelist to
  reject unknown operations early with a proper error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 12:53:53 -04:00
jared a3fbad19c9 Fix leading-zero ticket ID handling across API and UI
- dashboard.js: use String(cb.value) instead of parseInt() in
  getSelectedTicketIds() so zero-padded IDs like 000123456 are
  preserved when sent to bulk_operation.php
- DashboardView.php: remove (int) cast on data-ticket-id attribute
  for quick-status button; was stripping leading zeros
- TicketView.php: remove (int) cast on export URL ticket_id param
- update_ticket.php: preserve ticket_id as string via trim((string)...)
- add_comment.php: preserve ticket_id as string; validate with
  ctype_digit instead of (int) cast so comments are stored with the
  canonical zero-padded ID matching the tickets table
- export_tickets.php: validate singleId as string to avoid stripping
  leading zeros in the export endpoint
- notifications.php: preserve ticket_id strings in URLs and ticket
  ownership checks; index myTicketIds by both int and string forms
  for robust lookup regardless of how audit_log stored the ID
- TicketController.php: fix inline dependency insert — column was
  wrong (depends_on_ticket_id → depends_on_id) and bind types were
  wrong ("iii" → "ssi"); feature was silently broken

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 12:43:29 -04:00
jared d295d64f85 Fix leading-zero ticket ID handling across API and UI
- dashboard.js: use String(cb.value) instead of parseInt() in
  getSelectedTicketIds() so zero-padded IDs like 000123456 are
  preserved when sent to bulk_operation.php
- DashboardView.php: remove (int) cast on data-ticket-id attribute
  for quick-status button; was stripping leading zeros
- TicketView.php: remove (int) cast on export URL ticket_id param
- update_ticket.php: preserve ticket_id as string via trim((string)...)
- add_comment.php: preserve ticket_id as string; validate with
  ctype_digit instead of (int) cast so comments are stored with the
  canonical zero-padded ID matching the tickets table
- export_tickets.php: validate singleId as string to avoid stripping
  leading zeros in the export endpoint
- notifications.php: preserve ticket_id strings in URLs and ticket
  ownership checks; index myTicketIds by both int and string forms
  for robust lookup regardless of how audit_log stored the ID
- TicketController.php: fix inline dependency insert — column was
  wrong (depends_on_ticket_id → depends_on_id) and bind types were
  wrong ("iii" → "ssi"); feature was silently broken

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-11 12:43:18 -04:00
jared d6603d07f2 Fix bulk operation dropping tickets with leading-zero IDs, add query null-check
bulk_operation.php: ticket ID validation was converting IDs to int then back
to string, so '000123456' became '123456' which never matched the DB VARCHAR
key, silently rejecting ~11% of tickets from bulk operations. Now validates
with ctype_digit() to preserve leading zeros.

TicketModel::getTicketsByIds(): changed intval() to strval() and bind type
'i' to 's' so VARCHAR ticket_id columns are queried consistently as strings.

DashboardController::getCategoriesAndTypes(): added null check on query
result before calling fetch_assoc() to prevent TypeError if query fails.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 22:29:37 -04:00
jared d443caf059 Fix bulk operation dropping tickets with leading-zero IDs, add query null-check
bulk_operation.php: ticket ID validation was converting IDs to int then back
to string, so '000123456' became '123456' which never matched the DB VARCHAR
key, silently rejecting ~11% of tickets from bulk operations. Now validates
with ctype_digit() to preserve leading zeros.

TicketModel::getTicketsByIds(): changed intval() to strval() and bind type
'i' to 's' so VARCHAR ticket_id columns are queried consistently as strings.

DashboardController::getCategoriesAndTypes(): added null check on query
result before calling fetch_assoc() to prevent TypeError if query fails.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-10 22:29:14 -04:00
jared e9a033d4ef Add markdown cheat sheet modal to ticket comment editor
A '?' button next to the MD/Preview toggles opens a reference modal
covering all supported syntax: inline formatting, headings, lists,
task lists, tables, code blocks, footnotes, emoji shortcodes, and
ticket references.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 00:31:46 -04:00
jared 3a516c5424 Fix markdown preview missing lt-markdown class
The #markdownPreview div lacked the lt-markdown class, so CSS rules for
list-style (ul bullets, ol numbers), mark, del, task items etc. never
applied during live preview while typing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 00:30:13 -04:00
jared 74d1770cd6 Fix ordered list bullets and implement footnotes
- CSS: global reset `ul, ol { list-style: none }` was killing all bullets
  and numbers. Add list-style: disc/decimal back on .lt-markdown ul/ol.
  Remove duplicate ol rules.
- Footnotes: implement [^label] / [^label]: syntax. Uses placeholder
  approach (like code blocks) so <sup> tags aren't HTML-escaped. Renders
  inline superscript refs + numbered footnote block at bottom with
  back-links.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 00:28:02 -04:00
jared ddf1d236eb Fix markdown CSS not applying to comments — add lt-markdown class
comment-text divs with data-markdown were never getting the lt-markdown
class, so all scoped CSS (ul/ol/li bullets, mark, del, task items, etc.)
had no effect. Fixed in PHP template, JS comment builder, and
renderMarkdownComments().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 00:23:32 -04:00