deploy-2026.09.26-272
3
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c490d4f387 |
MCP: ticket links and similar-ticket search (#113)
Lint / PHP (phpcs PSR-12) (push) Successful in 48s
Lint / JS (eslint) (push) Successful in 17s
Lint / PHP requirements (version + extensions) (push) Successful in 49s
Lint / Notify on failure (push) Skipped
Security / PHP Security (semgrep) (push) Successful in 3m15s
Lint / Deploy (push) Successful in 4s
- get_ticket now returns `links` (blocks / blocked_by / relates_to /
duplicates / duplicated_by, phrased from this ticket's side, limited to
linked tickets the user can see) and `blocked` (any open blocked_by).
- find_similar_tickets (tickets:read): the possible-duplicates finder, by
title or by an existing ticket (which is excluded from the results).
- link_tickets / unlink_tickets (tickets:write). Marking a duplicate only
records the link. unlink also finds a link stored from the other side
("B blocked_by A" for "A blocks B").
api/ticket_dependencies.php's list/add/remove logic moves to
services/DependencyService.php, used by both (same checks and messages).
DependencyModel's remove methods now return rows removed, so removing a
link that is already gone no longer writes a "deleted" audit row.
Also includes the port in ToolScopeMiddleware's resource_metadata URL
(matches the 401's; no effect on prod, which has no port).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MGDKHiU5RJdo3dqQUDow3X
|
||
|
|
17be55bf8f |
Add MCP write tools: create_ticket, add_comment, update_status, assign_ticket (#111, phase 5)
Lint / PHP (phpcs PSR-12) (push) Successful in 20s
Lint / JS (eslint) (push) Successful in 8s
Lint / PHP requirements (version + extensions) (push) Successful in 20s
Lint / Notify on failure (push) Skipped
Security / PHP Security (semgrep) (push) Successful in 1m5s
Lint / Deploy (push) Successful in 2s
Each tool is a thin adapter over the same code path the web UI uses (TicketCreationService, CommentService, ApiTicketController, AssignmentService), run as the signed-in user, so permissions, Workflow Designer rules, audit entries, notifications and stats-cache invalidation are identical to doing the same thing in the browser. - Registered in ToolCatalog and listed in its WRITE_TOOLS, so ToolScopeMiddleware requires tickets:write for them. - Annotated readOnlyHint=false / destructiveHint=false (nothing deletes). - Input the web form constrains with dropdowns (priority 1-5, visibility, status) is validated in the tools. Assignees are "me", a username or "unassigned". - A ticket the user can't see reads as "not found" (never "access denied"), consistent with get_ticket. - update_status turns requires_comment into an actionable error and invalidates the stats cache like api/update_ticket.php does. Verified locally through the real pipeline (only JWT validation stubbed) against MariaDB with seeded workflow transitions: 30/30 checks, including a read-only token getting 403 insufficient_scope on create_ticket with nothing written; create/comment/status/assign attributed and audit-logged as the user; @mentions; internal visibility needing groups and staying hidden from non-members; an invisible confidential ticket not found for comment/status; requires_comment enforced, and closing with a reason persisted in one transaction; transitions outside the workflow refused; the admin/creator/assignee rule for assigning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MGDKHiU5RJdo3dqQUDow3X |
||
|
|
65deedc295 |
Add MCP identity mapping and read tools (#111, phase 4)
Lint / PHP (phpcs PSR-12) (push) Successful in 32s
Lint / JS (eslint) (push) Successful in 10s
Lint / PHP requirements (version + extensions) (push) Successful in 22s
Lint / Notify on failure (push) Skipped
Security / PHP Security (semgrep) (push) Successful in 1m4s
Lint / Deploy (push) Successful in 2s
MCP requests now run as the signed-in Tinker Tickets user, under exactly
the same rules as the web login, and expose the first two tools:
search_tickets and get_ticket.
Identity:
- IdentityMiddleware maps the validated token to a user with the same
checks as AuthMiddleware: the admin/employee group rule, now extracted
into helpers/AccessPolicy.php so both entry points share one copy, then
UserModel::syncUserFromAuthelia(), which creates/updates the row and
derives is_admin from groups.
- Claims are read from the validated token's server-side PSR-7 request
attributes, not from JSON-RPC _meta. The SDK's OAuthRequestMetaMiddleware
is deliberately not used: it array_merges into client-writable _meta,
so only the keys the validator happens to set are overwritten and a
client could inject others.
Scopes (ToolScopeMiddleware), enforced before dispatch:
- lifecycle messages need only a valid token; write tools (listed in
ToolCatalog, the single registry) need tickets:write; everything else
needs tickets:read, which tickets:write implies.
- Denials are the spec's step-up challenge: 403 +
WWW-Authenticate: Bearer error="insufficient_scope", scope=...,
resource_metadata=...
Tools (read-only, annotated readOnlyHint):
- search_tickets: text/status/priority/category/assignee ("me",
"unassigned", or a username), paginated, via TicketModel::getAllTickets
with the user's visibility filter. Defaults to every non-Closed status.
- get_ticket: details + comments, gated by canUserAccessTicket. A missing
ticket and a non-visible one return the same "not found".
Verified locally against a real MariaDB fixture (public, confidential,
internal+group, and closed tickets across two users), driving the real
pipeline (ToolCatalog, both middlewares, SDK transport) with only JWT
validation stubbed: 20/20 checks pass, including visibility parity per
user, confidential tickets hidden from non-owners, the group check
rejecting a user without admin/employee, a missing preferred_username
rejected, 403 insufficient_scope for a token without tickets:*, and
write implying read. Also exercised the stateless 2026-07-28 era (no
session, _meta + MCP-Protocol-Version/Mcp-Method/Mcp-Name headers), which
returns the same visibility-filtered results.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MGDKHiU5RJdo3dqQUDow3X
|