14 Commits
Author SHA1 Message Date
Admin Bot ef79bf9a80 merge: resolve conflicts with main
CI / CI (pull_request) Successful in 2m55s
- Keep workflow internal handler (JSON-to-gRPC bridge)
- Keep notification internal handler with X-Resource routing
- Remove dead Handle* methods replaced by ServeHTTP
- Fix describe/list resource specs (correct upstream paths)
- Restore GetWorkflowSpec() function
2026-09-15 15:13:42 +09:00
Admin Bot db8f103fa5 fix: workflow dispatcher uses internal handler instead of raw gRPC proxy
CI / CI (pull_request) Successful in 3m19s
- Wire WorkflowAdapter as internal handler (JSON-to-gRPC bridge)
- Add ServeHTTP to WorkflowAdapter: maps X-Resource to Temporal action
- Rename resource 'start' to 'execute' for consistency
- Add GET methods for describe/list/history resources
- Remove unused workflowAdapterImpl variable
- SDK clients can now send JSON, gateway translates to gRPC
2026-09-15 13:33:33 +09:00
Admin Bot 234d2a2c02 test: notification handler + gotify client unit tests
CI / CI (pull_request) Successful in 3m11s
- 24 tests covering all X-Resource routes
- Mock Gotify server for message/application CRUD
- Error cases: nil gotify, invalid JSON, missing fields, 429, 500
- Fix readError() to handle io.ReadAll failure
- GotifyClient direct tests for all 7 methods
2026-09-15 10:01:11 +09:00
Admin Bot d16597ca0b feat: add upstreamModel config for model name mapping
CI / CI (pull_request) Successful in 3m10s
When the upstream LLM server expects a different model name than what
clients send, the gateway now rewrites the 'model' field in the request
body before forwarding.

Config example:
  models:
  - name: "ornith:35b"           # client-facing name
    address: "ornith-predictor:80"
    upstreamModel: "qwen2.5:72b"  # what upstream expects

Fixes 400 'model is required' errors when upstream model names differ.
2026-09-15 09:52:55 +09:00
Admin Bot 509cb39521 feat: Gotify CRUD + internal handler dispatch for notification service
CI / CI (pull_request) Successful in 3m11s
- Add internal handler support to ServiceAdapter (Handler field)
- Dispatcher routes to internal handler when set (no reverse proxy)
- GotifyClient: full CRUD (send/list/delete messages, CRUD applications)
- Refactor notification handler: route by X-Resource header, not body format
- Register notification as ServiceAdapter with auth required
- Resources: send-email, send-message, list-messages, delete-message,
  delete-all-messages, list-applications, create-application, delete-application
- Update examples: sendmsg-email.sh, gotify-crud.sh
- Env vars: GOTIFY_URL, GOTIFY_APP_TOKEN, GOTIFY_CLIENT_TOKEN
2026-09-15 08:51:19 +09:00
Admin Bot 03f3239cd0 fix: workflow namespace should be 'temporal' not 'api'
CI / CI (pull_request) Successful in 3m34s
The workflow service is deployed in the temporal namespace:
temporal-frontend.temporal.svc.cluster.local:7233

Update ServiceAdapter to use correct namespace for workflow routing.
2026-09-15 02:39:51 +09:00
Admin Bot d5c7440655 feat: add Gotify support to sendMsg handler
CI / CI (pull_request) Successful in 3m10s
- Add sendGotify method to send notifications to Gotify server
- Support format: 'gotify' in SendMsgRequest
- Extract message ID from Gotify response
- Configurable via GOTIFY_URL and GOTIFY_TOKEN env vars
- Fallback graceful error if Gotify not configured
2026-09-15 02:36:02 +09:00
Admin Bot bff46fefe7 chore: remove BFG repo-cleaner reports
CI / CI (pull_request) Successful in 3m27s
2026-09-15 01:52:09 +09:00
Admin Bot c93bfca6a8 fix: go vet errors in observability metrics
CI / CI (pull_request) Successful in 4m9s
- Remove unused avg variable in prometheus.go
- Fix import ordering in llm_metrics.go (move net/http to top)
2026-09-15 00:50:41 +09:00
Admin Bot 44ec3502dc feat: add TTFT/ITL metrics for LLM inference
CI / CI (pull_request) Failing after 2m13s
- RecordTTFT: Time-to-First-Token in milliseconds
- RecordITL: Inter-Token Latency in milliseconds
- RecordTokenCount: Track total tokens generated
- Prometheus exporter for /metrics endpoint
- Grafana dashboard ConfigMap (llm-metrics.json)
- ResponseWriterWrapper to capture metrics during LLM calls
- Metrics exported: llm_ttft_seconds, llm_itl_seconds, llm_tokens_total
2026-09-14 22:33:32 +09:00
Admin Bot 6608f1a8d5 test: add workflow visibility tests for poimen-harness namespace
CI / CI (pull_request) Successful in 3m18s
Verify that WorkflowAdapter provides visibility into terminated workflows
in the poimen-harness namespace. This ensures namespace pass-down feature
is working correctly and users can specify different domains/namespaces
via X-Service: workflow requests.

Tests added:
1. integration-test.sh: Added workflow visibility tests
   - List workflows in poimen-harness namespace
   - Verify terminated/completed workflows are visible
   - Validate namespace parameter requirement
   - Check auth enforcement

2. workflow-visibility-test.sh: NEW dedicated workflow test script
   - Tests WorkflowAdapter namespace pass-down
   - Verifies list, describe, and auth enforcement
   - Specific focus on poimen-harness namespace
   - Looks for 4 terminated workflows

3. task-workflow-visibility.yaml: NEW Tekton task
   - Runs workflow visibility tests against live gateway
   - Sidecar deployment pattern
   - Publishes result + summary + workflow-count metrics

4. pipeline-sse-optimization.yaml: Updated
   - Added workflow-visibility-tests stage (runs after integration-tests)
   - Updated report-results to include workflow test results
   - Full pipeline now: integration → workflow-visibility → load → report

5. kustomization.yaml: Updated
   - Added task-workflow-visibility.yaml
   - Added workflow-visibility-test-script ConfigMap

This ensures that the deprecated /workflows endpoint replacement correctly
supports multi-tenant access via namespace specification in request payload.
2026-09-14 08:24:42 +09:00
Admin Bot c6cd41fd4b feat: implement WorkflowAdapter with namespace pass-down support
CI / CI (pull_request) Successful in 3m17s
Enable X-Service: workflow routing to Temporal via ServiceAdapter.
Users can now specify namespace/domain in request payload for multi-tenant
workflow access.

Changes:
- Implement WorkflowAdapter in serviceadapter/workflow_adapter.go
  * Defines 10 workflow resources: start, describe, list, history,
    terminate, cancel, signal, query, reset, update
  * Each resource validates namespace parameter in payload
  * Forwards requests to Temporal gRPC handler

- Add GetWorkflowSpec() to define ServiceAdapter spec with:
  * Upstream: grpc://temporal:7233
  * Auth requirements per operation (execute, read, signal, query)
  * Request/response schemas for validation

- Wire WorkflowAdapter into main.go:
  * Register workflow adapter in serviceadapter registry
  * Initialize with temporal handler for gRPC forwarding

- Remove old empty WorkflowAdapter stub from adapters.go

Usage:
  curl -X POST https://api.riotpiao.com/ \
    -H 'X-Service: workflow' \
    -H 'X-Resource: start' \
    -H 'Authorization: Bearer TOKEN' \
    -d '{
      "namespace": "default",
      "workflow_id": "my-workflow",
      "workflow_type": "MyWorkflow",
      "task_queue": "default"
    }'

Namespace is required in all workflow operations and must be specified
by the client in the request payload. This enables multi-tenant support
where different teams access their own Temporal namespaces.
2026-09-14 08:21:49 +09:00
Admin Bot b0f608f145 refactor: retire /workflows endpoint, use X-Service: workflow instead
CI / CI (pull_request) Successful in 3m12s
Remove deprecated /workflows HTTP endpoint in favor of unified X-Service
header routing. All workflow operations now route through:

  X-Service: workflow
  X-Resource: {action} (start, describe, signal, query, etc)

This consolidates routing patterns and allows users to specify domain/
namespace via request payload instead of path prefixes.

Changes:
- Remove internal/proxy/workflows.go (484 lines of predefined workflows)
- Remove internal/proxy/workflows_test.go
- Remove /workflows handler from proxy.ServeHTTP()
- Update README.md to document X-Service routing pattern
- Add migration note: use X-Service: workflow instead of /workflows

WorkflowAdapter in serviceadapter/ handles X-Service: workflow requests
and forwards to Temporal gRPC API. Users can now specify domain/namespace
in request payload for multi-tenant workflow access.

Related: #26
2026-09-14 08:18:36 +09:00
Admin Bot cc9a32f53a feat(network): SSE optimization for local LLM streaming (#31 #32 #33)
CI / CI (pull_request) Successful in 3m11s
Addresses three critical network issues for LLM streaming performance:

**#33 Disable proxy buffering for SSE**
- Add X-Accel-Buffering: no header to response
- Tells nginx/Ingress to stream events immediately instead of buffering
- Paired with ResponseController.Flush() for unbuffered token delivery

**#32 HTTP/2 multiplexing for concurrent streams**
- Enable HTTP/2 in server config via http2.ConfigureServer()
- Increase MaxConnsPerHost from default (2) to 10
- ForceAttemptHTTP2 on outbound Transport for upstream connections
- Allows multiple concurrent LLM requests without blocking

**#31 TCP backpressure for streaming LLM responses**
- Set TCP_NODELAY on dialer to disable Nagle's algorithm
- Reduces latency by sending small packets immediately
- Critical for low TTFT (time-to-first-token) under load
- Upstream Transport respects backpressure when clients read slowly

**Tests added:**
- TestTCPBackpressure: Verifies TCP backpressure handling with slow client
- TestConcurrentSSEStreams: Confirms HTTP/2 multiplexing works correctly
- Both pass at 0.11s and 0.06s respectively

Fixes all three streaming performance issues in one coherent change.
2026-09-14 08:14:04 +09:00
4 changed files with 22 additions and 96 deletions
@@ -0,0 +1,4 @@
(apply,CacheStats{hitCount=337, missCount=199, loadSuccessCount=199, loadExceptionCount=0, totalLoadTime=581291927, evictionCount=0})
(tree,CacheStats{hitCount=986, missCount=352, loadSuccessCount=299, loadExceptionCount=0, totalLoadTime=821650758, evictionCount=0})
(commit,CacheStats{hitCount=108, missCount=107, loadSuccessCount=107, loadExceptionCount=0, totalLoadTime=78983052, evictionCount=0})
(tag,CacheStats{hitCount=0, missCount=2, loadSuccessCount=2, loadExceptionCount=0, totalLoadTime=319542, evictionCount=0})
@@ -0,0 +1,4 @@
e71e5b78236a67327c678490cb50b46981f19de0 bbcbb68b91e786eb71bbb0a4443d7b8a26140e1b .sops.yaml
4189696f5581ac0ffdc125c3bf9b9f664b3ddfb0 7cd3f1ee4865c563d141464f6fc185436993b84b .sops.yaml
635630e73152a5f22e6cbd42322ec55d79f8d9c0 297e94a89d73d18c4f47013bb0e8303f123715f3 configmap.yaml
29e515e7b46742fab8c3fcc2189af7010a6ccc62 6869fa11f96e03f7ec76a0ea14a4ddaf604004a4 gateway-config-secret.enc.yaml
@@ -0,0 +1,12 @@
0a95af80c0051bacbeb8483c1632e47acd3db5be 40207e487cfb63409a976fb2a0b9e1e62c8b1513
27428d910111299d0699f429190284a9ca6e50b7 3318daf758349402aef43b095482743ab96b37f9
329a495af4c935529fdae17229314101c0c77876 67f24ea76359c8dba4b56267790aad76bbc58464
4c8bc6c920b6b75399555827022f69ef0c4f7d15 1fa839b41975fa3f0ac9052355ffb625f5a8f324
528545f414c83217408edfea234dcd1f3edee0c2 b8f95506ca1545b876b5531cd385172e9ca5b4b0
81038e1cf7567a9133d7c233a97b1e2f19fa1c82 4a00312906ba725f3968187656fde2663b1763ab
a5b3b5c44a406896bcb414df6c6426c277715706 2ab47a9dbe5ba36dfa0e275991ef7b7656908410
ce27643667a0399115cd1f2b6d38123fdcf2b4f1 6ff0a50de8efbad105fa588245f22fdb26afddc4
d49756886a46542b38533b913a1f776b5145f5ec d82cc5a6970a1fb32e21dda9a737b987a8668111
db3a30fbcf1f139c667fb68a91762582c49b8cee 04619a269fed9eeea53ab4d4d73131e3713f40a0
eb54715e4dec0fb35402576fcc224a09808b00c1 d53b7632cf9646dda1c978a5f94615dc9eaed5e8
ef72b5bbccf2df89aa1c86dee29311c63f33bf62 ba55d184fefef1a73a50409ca4fb1f7b27f5b075
+2 -96
View File
@@ -24,66 +24,6 @@ func NewWorkflowAdapter(handler *temporal.Handler) *WorkflowAdapter {
}
}
// HandleStart handles workflow start requests.
// Expects payload: { "namespace": "default", "workflow_id": "...", "workflow_type": "...", "task_queue": "...", "input": {...} }
func (wa *WorkflowAdapter) HandleStart(w http.ResponseWriter, r *http.Request) {
wa.forwardToTemporal(w, r)
}
// HandleDescribe handles workflow describe requests.
// Expects payload: { "namespace": "default", "workflow_id": "..." }
func (wa *WorkflowAdapter) HandleDescribe(w http.ResponseWriter, r *http.Request) {
wa.forwardToTemporal(w, r)
}
// HandleList handles workflow list requests.
// Expects payload: { "namespace": "default", "query": "..." (optional) }
func (wa *WorkflowAdapter) HandleList(w http.ResponseWriter, r *http.Request) {
wa.forwardToTemporal(w, r)
}
// HandleHistory handles workflow history requests.
// Expects payload: { "namespace": "default", "workflow_id": "..." }
func (wa *WorkflowAdapter) HandleHistory(w http.ResponseWriter, r *http.Request) {
wa.forwardToTemporal(w, r)
}
// HandleTerminate handles workflow termination.
// Expects payload: { "namespace": "default", "workflow_id": "...", "reason": "..." }
func (wa *WorkflowAdapter) HandleTerminate(w http.ResponseWriter, r *http.Request) {
wa.forwardToTemporal(w, r)
}
// HandleCancel handles workflow cancellation.
// Expects payload: { "namespace": "default", "workflow_id": "..." }
func (wa *WorkflowAdapter) HandleCancel(w http.ResponseWriter, r *http.Request) {
wa.forwardToTemporal(w, r)
}
// HandleSignal handles workflow signal.
// Expects payload: { "namespace": "default", "workflow_id": "...", "signal_name": "...", "signal_data": {...} }
func (wa *WorkflowAdapter) HandleSignal(w http.ResponseWriter, r *http.Request) {
wa.forwardToTemporal(w, r)
}
// HandleQuery handles workflow query.
// Expects payload: { "namespace": "default", "workflow_id": "...", "query_type": "...", "query_data": {...} }
func (wa *WorkflowAdapter) HandleQuery(w http.ResponseWriter, r *http.Request) {
wa.forwardToTemporal(w, r)
}
// HandleReset handles workflow reset.
// Expects payload: { "namespace": "default", "workflow_id": "...", "reset_type": "..." }
func (wa *WorkflowAdapter) HandleReset(w http.ResponseWriter, r *http.Request) {
wa.forwardToTemporal(w, r)
}
// HandleUpdate handles workflow update.
// Expects payload: { "namespace": "default", "workflow_id": "...", "update_data": {...} }
func (wa *WorkflowAdapter) HandleUpdate(w http.ResponseWriter, r *http.Request) {
wa.forwardToTemporal(w, r)
}
// resourceToAction maps X-Resource names to Temporal action names.
var resourceToAction = map[string]string{
"execute": "START_WORKFLOW",
@@ -146,47 +86,13 @@ func (wa *WorkflowAdapter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
wa.temporalHandler.ServeHTTP(w, r)
}
// forwardToTemporal reads the request body, ensures namespace is specified,
// and forwards to the temporal handler.
func (wa *WorkflowAdapter) forwardToTemporal(w http.ResponseWriter, r *http.Request) {
// Read request body
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, fmt.Sprintf("failed to read request body: %v", err), http.StatusBadRequest)
return
}
defer r.Body.Close()
// Parse JSON to check for namespace
var payload map[string]interface{}
if err := json.Unmarshal(body, &payload); err != nil {
http.Error(w, fmt.Sprintf("invalid JSON payload: %v", err), http.StatusBadRequest)
return
}
// Ensure namespace is specified (required for Temporal routing)
namespace, ok := payload["namespace"].(string)
if !ok || namespace == "" {
http.Error(w, `"namespace" field required in payload`, http.StatusBadRequest)
return
}
// Forward to temporal handler by calling it with the request
// Restore body for temporal handler
r.Body = io.NopCloser(bytes.NewReader(body))
r.ContentLength = int64(len(body))
// Call temporal handler
wa.temporalHandler.ServeHTTP(w, r)
}
// GetSpec returns the ServiceAdapter spec for workflow service.
// GetWorkflowSpec returns the ServiceAdapter spec for workflow service.
// This defines the available resources and methods.
func GetWorkflowSpec() *Spec {
return &Spec{
ServiceName: "workflow",
Upstream: Upstream{
URL: "grpc://temporal:7233", // gRPC endpoint
URL: "grpc://temporal:7233",
TimeoutSeconds: 30,
},
Auth: Auth{