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
1 change: 1 addition & 0 deletions tools/ci/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This directory keeps CI gate scripts and smoke tests.
- Produces a filtered `Cobertura.xml` under `artifacts/coverage/<timestamp>-ci-gate/report/` after applying assembly/file exclusions for non-core shells/adapters such as `Aevatar.Tools.*`, `Aevatar.Studio.*`, `Aevatar.Authentication.*`, and host app entrypoints.
- `tools/ci/architecture_guards.sh`: architecture/static guards (includes projection route mapping guard).
- `tools/ci/channel_mega_interface_guard.sh`: blocks regressions that merge channel runtime and outbound methods back into one mega interface.
- `tools/ci/frontend_static_boundary_guard.sh`: blocks frontend regressions that call actor-state/replay/projection-refresh endpoints, parse actorId prefixes, or depend on internal EventEnvelope routing fields.
- `tools/ci/fetch_latest_ci_failure.sh`: downloads the latest failed GitHub Actions run metadata and failed logs into `artifacts/ci-failures/latest/` via `gh`.
- `tools/ci/test_stability_guards.sh`: polling/unstable test pattern guard.
- `tools/ci/solution_split_guards.sh`: split build guard.
Expand Down
3 changes: 2 additions & 1 deletion tools/ci/architecture_guards.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ bash "${SCRIPT_DIR}/channel_relay_nyx_chat_direct_create_guard.sh"
bash "${SCRIPT_DIR}/channel_tombstone_proto_field_guard.sh"
bash "${SCRIPT_DIR}/agent_tool_delivery_target_reader_guard.sh"
bash "${SCRIPT_DIR}/studio_projection_readmodel_registration_guard.sh"
bash "${SCRIPT_DIR}/frontend_static_boundary_guard.sh"

secret_store_scan_roots=()
while IFS= read -r host_dir; do
Expand Down Expand Up @@ -360,7 +361,7 @@ END {
exit 1;
}
}
'
'
)"
state_direct_mutation_status=$?
set -e
Expand Down
84 changes: 84 additions & 0 deletions tools/ci/frontend_static_boundary_guard.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
#
# Frontend static boundary guard.
#
# Prevents frontend code from violating CQRS / Actor architecture boundaries
# defined in CLAUDE.md and docs/canon/cqrs-projection.md:
#
# 1. Calling actor-state, event-replay, or projection-refresh endpoints
# (queries must go through readmodel, not replay or refresh).
# 2. Parsing actorId string prefixes for business semantics
# (actorId is an opaque address per CLAUDE.md "actorId 对调用方不透明").
# 3. Accessing EventEnvelope route/runtime/propagation internals
# (envelope internals are delivery context, not business completion semantics).
#
# Failure mode: a frontend PR introduces direct actor-state reads, event-replay
# polling, or actorId prefix parsing; CI fails fast with the offending file.

set -euo pipefail

SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"
cd "${REPO_ROOT}"

FRONTEND_SRC="apps/aevatar-console-web/src"

if [[ ! -d "${FRONTEND_SRC}" ]]; then
echo "frontend_static_boundary_guard: skipped (${FRONTEND_SRC} does not exist)"
exit 0
fi

FRONTEND_EXCLUDES=(
-g '!**/*.test.*'
-g '!**/__tests__/**'
-g '!**/.umi*/**'
-g '!**/node_modules/**'
)

failures=0

forbidden_route_hits="$(
rg -n '(/actor-state|/events/replay|/projections?/refresh)' "${FRONTEND_SRC}" \
-g '*.ts' -g '*.tsx' -g '*.js' -g '*.jsx' \
"${FRONTEND_EXCLUDES[@]}" \
|| true
)"

if [[ -n "${forbidden_route_hits}" ]]; then
echo "${forbidden_route_hits}"
echo "Frontend code must not call actor-state, event replay, or projection refresh endpoints."
failures=1
fi

actor_id_parsing_hits="$(
rg -n -P '\b[\w$]*[Aa]ctorId\b\s*\??\s*\.\s*(startsWith|includes|indexOf|match|split)\s*\(' "${FRONTEND_SRC}" \
-g '*.ts' -g '*.tsx' -g '*.js' -g '*.jsx' \
"${FRONTEND_EXCLUDES[@]}" \
|| true
)"

if [[ -n "${actor_id_parsing_hits}" ]]; then
echo "${actor_id_parsing_hits}"
echo "Frontend code must treat actorId as an opaque address, not parse it for business semantics."
failures=1
fi

event_envelope_field_hits="$(
rg -n -P '\b(eventEnvelope|actorEnvelope|runtimeEnvelope|workflowRunEventEnvelope)\b\s*\??\s*\.\s*(route|runtime|propagation)\b' "${FRONTEND_SRC}" \
-g '*.ts' -g '*.tsx' -g '*.js' -g '*.jsx' \
"${FRONTEND_EXCLUDES[@]}" \
|| true
)"

if [[ -n "${event_envelope_field_hits}" ]]; then
echo "${event_envelope_field_hits}"
echo "Frontend code must not branch on EventEnvelope route/runtime/propagation internals."
failures=1
fi

if [[ "${failures}" -ne 0 ]]; then
echo "frontend_static_boundary_guard: failed"
exit 1
fi

echo "frontend_static_boundary_guard: ok"
Loading