Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion authbridge/sparc-service/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ COPY sparc_service ./sparc_service
RUN pip install --upgrade pip && pip install .

# Drop privileges.
RUN useradd --create-home --uid 10001 sparc
RUN useradd --create-home --uid 10001 sparc && chown -R sparc:sparc /app
USER sparc

EXPOSE 8090
Expand Down
7 changes: 6 additions & 1 deletion authbridge/sparc-service/deploy/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ image: ## Build the sparc-service image locally and load it into kind
@echo "[*] building $(IMAGE)"
$(CONTAINER_RUNTIME) build -t $(IMAGE) ..
@echo "[*] kind load $(IMAGE) into $(KIND_CLUSTER_NAME)"
kind load docker-image $(IMAGE) --name $(KIND_CLUSTER_NAME)
@# kind load docker-image fails on Linux with rootful Podman — use image-archive instead.
if kind load docker-image $(IMAGE) --name $(KIND_CLUSTER_NAME) 2>/dev/null; then \
true; \
else \
$(CONTAINER_RUNTIME) save $(IMAGE) | kind load image-archive /dev/stdin --name $(KIND_CLUSTER_NAME); \
fi
Comment on lines +54 to +59

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/sh
set -eu

kind version
nodes="$(kind get nodes --name rossoctl | sed '/^[[:space:]]*$/d' | wc -l | tr -d ' ')"
printf 'rossoctl node count: %s\n' "$nodes"

if [ "$nodes" -gt 1 ]; then
  echo "The /dev/stdin fallback must use a temporary archive for this cluster."
  exit 1
fi

Repository: rossoctl/cortex

Length of output: 195


🏁 Script executed:

#!/bin/sh
set -eu

# Locate and inspect the referenced Makefile and relevant kind load behavior.
if [ -f authbridge/sparc-service/deploy/Makefile ]; then
  sed -n '1,90p' authbridge/sparc-service/deploy/Makefile
else
  printf 'authbridge/sparc-service/deploy/Makefile not found\n'
  find . -path '*/deploy/Makefile' -maxdepth 5
fi

printf '\n--- kind references ---\n'
rg -n "kind load image-archive|kind load docker-image|image-archive|/dev/stdin|KIND_CLUSTER_NAME|CONTAINER_RUNTIME" .

Repository: rossoctl/cortex

Length of output: 18619


🌐 Web query:

kubernetes-sigs kind image-archive load archive opens archive once per node kind v0.32.0

💡 Result:

In kind v0.32.0, the kind load image-archive command processes each specified archive file individually [1][2]. When multiple archives are provided, kind opens and loads each archive file sequentially for the target nodes [1]. For each node selected to receive an image, the implementation opens the archive file on the host using os.Open and streams it into the node via a pipe (or by streaming the reader) to the ctr command (containerd's CLI) running inside the node [3][4]. If you specify multiple nodes, the archive must be read and transferred to each node independently [4][5]. The kind implementation does not perform an "open once" operation across multiple nodes; rather, it handles the file reading and streaming process per node, as necessitated by the need to transmit the archive data into each individual container environment [3][4][5]. If you are concerned about performance or resource usage when loading to many nodes, using process substitution in your shell—such as kind load image-archive <(cat archive.tar)—or creating a single, combined tarball containing all desired images is generally recommended to optimize the transfer and loading process [1][2][5].

Citations:


🌐 Web query:

github.com kubernetes-sigs kind v0.32.0 pkg/cmd/kind/load/image-archive image-archive.go nodes archive loop

💡 Result:

In KIND (Kubernetes in Docker) version v0.32.0, the command kind load image-archive supports loading one or more image archives (tarballs) into cluster nodes [1][2]. The implementation of image-archive.go iterates over the provided arguments (the paths to the image archives) and performs the loading process for each archive [1]. When loading, the tool targets the cluster nodes—either all nodes by default or a specific set if defined by the user—and executes the loading logic for each archive on those nodes [1][3]. Historically, the command only supported loading a single archive [4], but support for multiple archives was added in pull request #2891 [1]. When multiple archives are provided, the code iterates through each file path and performs the load operation sequentially [1]. It does not involve a loop over nodes that creates redundant loading logic in the same way reported for the docker-image command (which, unlike image-archive, has faced issues where it would redundantly process nodes for each image) [5][3]. For technical details, the image-archive command ultimately uses nodeutils.LoadImageArchive to stream the archive file content to the node, where it is imported into the node's container runtime (typically containerd) [3][6]. The command also supports streaming from stdin by using - as the archive name [7].

Citations:


🌐 Web query:

kubernetes-sigs/kind/pkg/cluster/nodeutils LoadImageArchive source

💡 Result:

The LoadImageArchive function in the kubernetes-sigs/kind repository, located in pkg/cluster/nodeutils/util.go, is responsible for loading a container image archive onto a specified KIND node [1][2]. It functions by executing the containerd command-line tool (ctr) inside the target node [1]. The function takes a node (nodes.Node) and an io.Reader (which provides the image archive data) as arguments [1]. It streams this archive data into the ctr command's standard input [1]. The specific command executed is: ctr --namespace=k8s.io images import --all-platforms --digests --snapshotter= - [1] Key aspects of the implementation include: - Snapshotter Detection: The function dynamically detects the appropriate containerd snapshotter to use by inspecting the node's containerd configuration [1]. It parses the configuration to determine the correct snapshotter based on the containerd version (supporting config versions 2, 3, and 4) [1]. - Input Handling: By using an io.Reader and piping it to the ctr command (indicated by the final "-" argument), the function supports efficient image loading from various sources, including file streams and pipes [1][3]. This utility is used by higher-level KIND commands, such as kind load docker-image, to facilitate the transfer of container images from the host environment into the KIND cluster nodes [4][5][6].

Citations:


🏁 Script executed:

#!/bin/sh
set -eu

printf '--- local archive loading scripts nearby ---\n'
cat -n local-build-and-test.sh | sed -n '1,45p'

printf '\n--- makefile nodes target count/default ---\n'
rg -n "control-plane|worker|node count|nodes" authbridge/sparc-service/deploy/Makefile .github README.md LOCAL_TESTING_GUIDE.md || true

Repository: rossoctl/cortex

Length of output: 244


🏁 Script executed:

#!/bin/sh
set -eu

printf '%s\n' '--- local archive loading scripts nearby ---'
sed -n '1,45p' local-build-and-test.sh

printf '%s\n' ''
printf '%s\n' '--- makefile nodes target count/default ---'
rg -n "control-plane|worker|node count|nodes" authbridge/sparc-service/deploy/Makefile .github README.md LOCAL_TESTING_GUIDE.md || true

Repository: rossoctl/cortex

Length of output: 2197


🌐 Web query:

github.com kubernetes-sigs/kind v0.32.0 pkg/cmd/kind/load/image-archive/image-archive.go func RunLoadImageArchive os.Args args load

💡 Result:

In kind v0.32.0, the function RunLoadImageArchive (located in pkg/cmd/kind/load/image-archive/image-archive.go) handles the loading of container image archives into cluster nodes. The command processes image archives passed as arguments in os.Args. Specifically: 1. Handling of Arguments: The command accepts one or more image archive file paths as arguments [1]. While earlier versions of kind (prior to PR #2891) only processed the first argument [1][2], current implementations iterate through all provided arguments (args) [1]. 2. Processing Logic: Within the command's execution flow, each provided file path (representing a tar archive) is opened by the host [3][4]. The kind tool then utilizes internal node utilities to stream the archive content into the target cluster nodes [3][4]. 3. Stdin Support: As of recent versions, users can pass "-" as an argument to indicate that the image archive should be read from standard input (stdin) [5]. This allows for workflows that pipe output directly into kind, such as docker save my-image:latest | kind load image-archive - [6][5]. The command uses the concurrent.UntilError package to efficiently distribute and load the specified archives across the selected cluster nodes in parallel [3]. If no specific nodes are selected via flags, the command defaults to loading the archives into all available cluster nodes [3].

Citations:


Materialize the archive before loading it to kind.

This fallback streams one tarball into kind load image-archive. For multi-node kind clusters, kind can load the selected nodes concurrently, so the pipe can be split incorrectly. Save to a temporary archive first, chain the save with &&, then load that file.

Proposed fix
 else \
-  $(CONTAINER_RUNTIME) save $(IMAGE) | kind load image-archive /dev/stdin --name $(KIND_CLUSTER_NAME); \
+  archive=$$(mktemp) || exit 1; \
+  trap 'rm -f "$$archive"' EXIT; \
+  $(CONTAINER_RUNTIME) save $(IMAGE) > "$$archive" && \
+  kind load image-archive "$$archive" --name $(KIND_CLUSTER_NAME); \
 fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@# kind load docker-image fails on Linux with rootful Podman — use image-archive instead.
if kind load docker-image $(IMAGE) --name $(KIND_CLUSTER_NAME) 2>/dev/null; then \
true; \
else \
$(CONTAINER_RUNTIME) save $(IMAGE) | kind load image-archive /dev/stdin --name $(KIND_CLUSTER_NAME); \
fi
@# kind load docker-image fails on Linux with rootful Podman — use image-archive instead.
if kind load docker-image $(IMAGE) --name $(KIND_CLUSTER_NAME) 2>/dev/null; then \
true; \
else \
archive=$$(mktemp) || exit 1; \
trap 'rm -f "$$archive"' EXIT; \
$(CONTAINER_RUNTIME) save $(IMAGE) > "$$archive" && \
kind load image-archive "$$archive" --name $(KIND_CLUSTER_NAME); \
fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@authbridge/sparc-service/deploy/Makefile` around lines 54 - 59, Update the
fallback in the kind image-loading command to save $(IMAGE) to a temporary
archive first, chaining the container runtime save with && before invoking kind
load image-archive on that file. Preserve the existing fallback behavior and
ensure the temporary archive is cleaned up after loading.

@# Let containerd resolve the bare docker.io/library/<img> ref kubelet uses.
-$(CONTAINER_RUNTIME) exec $(KIND_NODE) ctr -n k8s.io images tag localhost/$(IMAGE) docker.io/library/$(IMAGE) >/dev/null 2>&1 || true

Expand Down
10 changes: 9 additions & 1 deletion authbridge/sparc-service/sparc_service/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,16 @@


def main() -> None:
import logging
import os
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s:%(name)s:%(message)s", datefmt="%Y-%m-%dT%H:%M:%SZ")
# Demote noisy third-party loggers — their INFO adds no operational value
logging.getLogger("LiteLLM").setLevel(logging.WARNING)
if os.getenv("SPARC_DEBUG_LLM", "").strip().lower() in ("1", "true", "yes"):
logging.getLogger("sparc_service.llm_debug").setLevel(logging.DEBUG)
logging.getLogger("altk").setLevel(logging.DEBUG)
settings = Settings.from_env()
uvicorn.run("sparc_service.api:app", host=settings.host, port=settings.port, log_level="info")
uvicorn.run("sparc_service.api:app", host=settings.host, port=settings.port, log_level="info", access_log=False)


if __name__ == "__main__":
Expand Down
66 changes: 66 additions & 0 deletions authbridge/sparc-service/sparc_service/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,18 @@
POST /reflect — run SPARC on a proposed tool call, return the verdict.
GET /healthz — liveness (always ok if the process is up).
GET /readyz — readiness (config valid and component buildable).

Log levels:
INFO — clean operational log: startup skip list + evaluated verdicts only.
DEBUG — adds per-call skip entries and full request payloads
(payloads only when SPARC_LOG_REQUESTS=true).
"""

from __future__ import annotations

import json
import logging
import os

from fastapi import FastAPI, HTTPException
from fastapi.concurrency import run_in_threadpool
Expand All @@ -19,6 +26,43 @@

log = logging.getLogger(__name__)

# SPARC_LOG_REQUESTS=true — log the full incoming ReflectRequest JSON at DEBUG.
# Useful for diagnosing unexpected tool argument keys. Disabled by default —
# payloads can be large. Requires LOG_LEVEL=DEBUG to be visible.
_LOG_REQUESTS: bool = os.getenv("SPARC_LOG_REQUESTS", "").strip().lower() in {"1", "true", "yes"}
Comment on lines +29 to +32

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C 3 'LOG_LEVEL|SPARC_LOG_REQUESTS|basicConfig|setLevel\(' authbridge/sparc-service

Repository: rossoctl/cortex

Length of output: 3733


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Relevant files:\n'
fd -a '__main__.py|api.py' authbridge/sparc-service | sed 's#^\./##'

printf '\n__main__.py:\n'
cat -n authbridge/sparc-service/sparc_service/__main__.py | sed -n '1,40p'

printf '\napi.py imports and request handler section:\n'
cat -n authbridge/sparc-service/sparc_service/api.py | sed -n '1,130p'

printf '\nCheck whether logger hierarchy enables sparc_service(api) DEBUG under root INFO:\n'
python3 - <<'PY'
import logging
logging.basicConfig(level=logging.INFO)
logging.getLogger("LiteLLM").setLevel(logging.WARNING)
log = logging.getLogger("sparc_service.api")
print("root level:", logging.root.level)
print("sparc_service.api effective level:", log.getEffectiveLevel())
print("debug enabled:", log.isEnabledFor(logging.DEBUG))
PY

Repository: rossoctl/cortex

Length of output: 8201


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Relevant files:\n'
fd -a '__main__.py|api.py' authbridge/sparc-service | sed 's#^\./##'

printf '\n__main__.py:\n'
cat -n authbridge/sparc-service/sparc_service/__main__.py | sed -n '1,40p'

printf '\napi.py imports and request handler section:\n'
cat -n authbridge/sparc-service/sparc_service/api.py | sed -n '1,130p'

printf '\nCheck whether logger hierarchy enables sparc_service.api DEBUG under root INFO:\n'
python3 - <<'PY'
import logging
logging.basicConfig(level=logging.INFO)
logging.getLogger("LiteLLM").setLevel(logging.WARNING)
log = logging.getLogger("sparc_service.api")
print("root level:", logging.root.level)
print("sparc_service.api effective level:", log.getEffectiveLevel())
print("debug enabled:", log.isEnabledFor(logging.DEBUG))
PY

Repository: rossoctl/cortex

Length of output: 8200


Make SPARC_LOG_REQUESTS debug output reachable.

authbridge/sparc-service/sparc_service/api.py calls log.debug(...) for request payloads, but authbridge/sparc-service/sparc_service/__main__.py configures the root logger at INFO and only enables sparc_service.llm_debug when SPARC_DEBUG_LLM is set. Support LOG_LEVEL, or enable sparc_service.api debug logs when SPARC_LOG_REQUESTS=true.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@authbridge/sparc-service/sparc_service/api.py` around lines 29 - 32, Update
the logging configuration in __main__.py so LOG_LEVEL controls the root logger
level, allowing DEBUG when configured, and ensure sparc_service.api debug
logging is enabled when SPARC_LOG_REQUESTS is true. Preserve the existing
SPARC_DEBUG_LLM-specific behavior and default INFO level.


# SPARC_STRIP_TOOL_ARG_KEYS — comma-separated keys to remove from every
# tool_calls[].function.arguments before SPARC evaluates the call.
# Example: SPARC_STRIP_TOOL_ARG_KEYS=session_id,request_id
_STRIP_KEYS: frozenset[str] = frozenset(
k.strip() for k in os.getenv("SPARC_STRIP_TOOL_ARG_KEYS", "").split(",") if k.strip()
)

# SPARC_SKIP_TOOLS — comma-separated tool names to auto-approve without SPARC.
# Use for infrastructure tools (e.g. message, calculate) that have no policy
# risk and would cause false-positive rejects.
# Example: SPARC_SKIP_TOOLS=message,calculate
_SKIP_TOOLS: frozenset[str] = frozenset(
t.strip() for t in os.getenv("SPARC_SKIP_TOOLS", "").split(",") if t.strip()
)


def _strip_tool_arg_keys(tool_calls: list[dict], keys: frozenset[str]) -> list[dict]:
"""Return a copy of tool_calls with the named argument keys removed."""
result = []
for tc in tool_calls:
fn = tc.get("function", {})
raw_args = fn.get("arguments", "")
try:
args = json.loads(raw_args) if isinstance(raw_args, str) else raw_args
if isinstance(args, dict):
args = {k: v for k, v in args.items() if k not in keys}
new_args = json.dumps(args) if isinstance(args, dict) else raw_args
except (json.JSONDecodeError, TypeError):
new_args = raw_args
result.append({**tc, "function": {**fn, "arguments": new_args}})
return result


def create_app(engine: ReflectionEngine | None = None) -> FastAPI:
"""Build the FastAPI app. Inject ``engine`` in tests; defaults to env config."""
Expand All @@ -33,6 +77,10 @@
app.state.engine = engine
app.state.settings = settings

# INFO: announce skip list once at startup so operators know what is bypassed
if _SKIP_TOOLS:
log.info("SPARC_SKIP_TOOLS: the following tools will be auto-approved without evaluation: %s", sorted(_SKIP_TOOLS))

@app.get("/healthz")
def healthz() -> dict[str, object]:
return {
Expand All @@ -51,6 +99,24 @@

@app.post("/reflect", response_model=ReflectResponse)
async def reflect(request: ReflectRequest) -> ReflectResponse:
# DEBUG: full request payload — only when SPARC_LOG_REQUESTS=true
if _LOG_REQUESTS:
log.debug("incoming reflect request: %s", request.model_dump_json())

if _STRIP_KEYS and request.tool_calls:
request = request.model_copy(
update={"tool_calls": _strip_tool_arg_keys(request.tool_calls, _STRIP_KEYS)}
)
if _LOG_REQUESTS:
log.debug("after strip (%s): tool_calls=%s", sorted(_STRIP_KEYS), request.tool_calls)

if _SKIP_TOOLS and request.tool_calls:
tool_name = request.tool_calls[0].get("function", {}).get("name", "")
if tool_name in _SKIP_TOOLS:
# DEBUG: per-call skip entry — visible only at DEBUG level
log.debug("reflect tool=%s skipped (SPARC_SKIP_TOOLS)", tool_name)
return ReflectResponse(decision="approve", issues=[], overall_avg_score=None, execution_time_ms=None)
Comment on lines +113 to +118

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not approve a mixed tool-call request from its first tool name.

Line 115 approves the whole tool_calls batch when only the first call matches _SKIP_TOOLS. A request can place an infrastructure tool first and an enforced tool later. Require every call to be an allowed infrastructure tool before the fast path. Reject or evaluate the batch otherwise. Validate _SKIP_TOOLS against a fixed infrastructure allowlist at startup.

As per coding guidelines, do not bypass authentication or policy for traffic that requires IBAC or token-exchange enforcement; listener.skip_hosts is reserved for identifiable infrastructure traffic because matched requests bypass plugins and session recording entirely.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@authbridge/sparc-service/sparc_service/api.py` around lines 113 - 118, The
skip fast path in the request handling logic must approve a batch only when
every tool call is in the fixed infrastructure allowlist and `_SKIP_TOOLS`;
otherwise continue normal evaluation or rejection. Add startup validation
ensuring `_SKIP_TOOLS` contains only allowlisted infrastructure tools,
preserving authentication, IBAC, token-exchange, plugin, and session-recording
enforcement for all other traffic.

Source: Coding guidelines


# SPARCReflectionComponent.process is synchronous (and CPU/IO bound on the
# LLM call); run it off the event loop so the service stays responsive.
try:
Expand Down
46 changes: 41 additions & 5 deletions authbridge/sparc-service/sparc_service/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@

from __future__ import annotations

import json
import logging
import threading
from dataclasses import replace
from datetime import datetime, timezone
from typing import Any, Callable

from .models import ReflectionIssue, ReflectRequest, ReflectResponse
Expand Down Expand Up @@ -118,13 +120,47 @@ def reflect(self, request: ReflectRequest) -> ReflectResponse:
decision = _decision_str(reflection.decision)
score = _extract_overall_score(raw_pipeline)
execution_ms = getattr(output, "execution_time_ms", None)

# Extract tool name + args from the first tool call for correlation.
first_tc = request.tool_calls[0] if request.tool_calls else {}
fn = first_tc.get("function", {})
if not fn:
log.warning("reflect: tool_calls[0] has no 'function' key; tool correlation unavailable. call=%s", first_tc)
tool_name = fn.get("name", "-")
raw_args = fn.get("arguments", "{}")
try:
tool_args = json.loads(raw_args) if isinstance(raw_args, str) else raw_args
except (json.JSONDecodeError, TypeError):
tool_args = raw_args
try:
args_str = json.dumps(tool_args, separators=(",", ":"))
except (TypeError, ValueError):
args_str = repr(tool_args)
Comment on lines +124 to +138

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Remove raw request data from service diagnostics.

The diagnostics log user-controlled tool arguments, complete request payloads, prompts, and provider results. These values can contain credentials or personal data. Log redacted, allowlisted metadata only.

  • authbridge/sparc-service/sparc_service/engine.py#L124-L138: do not log first_tc or serialized arguments without redaction.
  • authbridge/sparc-service/sparc_service/engine.py#L151-L163: remove raw args from INFO and DEBUG telemetry.
  • authbridge/sparc-service/sparc_service/api.py#L102-L111: redact before the first request log event.
  • authbridge/sparc-service/sparc_service/providers.py#L134-L151: redact prompt, schema, and result fields before diagnostic logging.
🧰 Tools
🪛 ast-grep (0.45.0)

[info] 135-135: use jsonify instead of json.dumps for JSON output
Context: json.dumps(tool_args, separators=(",", ":"))
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

📍 Affects 3 files
  • authbridge/sparc-service/sparc_service/engine.py#L124-L138 (this comment)
  • authbridge/sparc-service/sparc_service/engine.py#L151-L163
  • authbridge/sparc-service/sparc_service/api.py#L102-L111
  • authbridge/sparc-service/sparc_service/providers.py#L134-L151
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@authbridge/sparc-service/sparc_service/engine.py` around lines 124 - 138,
Remove raw user-controlled data from diagnostics: in
authbridge/sparc-service/sparc_service/engine.py lines 124-138, stop logging
first_tc or unredacted serialized arguments; in lines 151-163, remove raw args
from INFO and DEBUG telemetry. In authbridge/sparc-service/sparc_service/api.py
lines 102-111, apply the approved redaction and allowlisted metadata before the
first request log event. In authbridge/sparc-service/sparc_service/providers.py
lines 134-151, redact prompt, schema, and result fields before diagnostic
logging, preserving only safe allowlisted metadata.


ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
score_str = f"{score:.2f}" if score is not None else "-"
ms_str = f"{execution_ms:.1f}" if isinstance(execution_ms, (int, float)) else "-"

def _tok(*keys: str) -> str:
for k in keys:
v = raw_pipeline.get(k)
if v is not None:
return str(v)
return "-"

log.info(
"reflect session=%s track=%s decision=%s score=%s ms=%s",
request.session_id or "-",
"reflect ts=%s tool=%s args=%s decision=%s score=%s ms=%s",
ts, tool_name, args_str, decision, score_str, ms_str,
)
log.debug(
"reflect ts=%s tool=%s args=%s decision=%s score=%s ms=%s"
" track=%s session=%s tokens_in=%s tokens_out=%s messages=%s",
ts, tool_name, args_str, decision, score_str, ms_str,
track,
decision,
f"{score:.2f}" if score is not None else "-",
f"{execution_ms:.1f}" if isinstance(execution_ms, (int, float)) else "-",
request.session_id or "-",
_tok("tokens_in", "input_tokens"),
_tok("tokens_out", "output_tokens"),
len(request.messages),
)

return ReflectResponse(
Expand Down
Loading
Loading