Files
secs-gem/docker-compose.yml
T
raphael 9876dd9b5a
tests / build-and-test (push) Successful in 2m59s
tests / thread-sanitizer (push) Successful in 3m36s
tests / tshark-dissector (push) Successful in 2m25s
tests / secs4j-interop (push) Successful in 59s
tests / python-interop (push) Successful in 3m20s
tests / libfuzzer (push) Successful in 3m40s
feat(daemon): D10 carriers + E16 ops RPCs + stress test + virtual fab
Completes the daemon's GEM300 surface and adds two new test tiers.

D10 — E87 carriers: CarrierStore gains the HandlerSlot observer pattern
(add_id/slot_map/access_handler). The daemon's id-observer forwards host
S3F17 decisions onto the Subscribe stream as CarrierAction (PROCEED on a
Confirmed transition, CANCEL on CancelCarrier); ReportCarrier drives the
flow tool-side: WAITING creates the carrier + records the slot map,
IN_ACCESS/COMPLETE advance the access FSM (INVALID_OBJECT on unknown,
CANNOT_DO_NOW on an illegal transition).

E16 — operations RPCs: Describe (full name inventory: variables/events/
alarms/commands/constants + device header), FlushSpool (purge or drain),
SendTerminalMessage (S10F1 tool->host, honest CANNOT_DO_NOW when no host
and stream 10 isn't spoolable).

Stream responsiveness: Subscribe/WatchHealth poll at 100ms (was 500ms) so a
cancelled stream frees its sync-server worker thread promptly — this was
found by the new stress test, which hung under Subscribe churn at 500ms.

Tests:
- A randomized concurrent RPC stress case: 4 threads x 250 seeded ops
  (set/get/fire/alarm/control-state/describe + Subscribe churn), asserts no
  failed RPC and a still-responsive engine afterward; prints its seed; a
  strong TSan target.
- A virtual fab (interop/virtual_fab.py + the `fab` compose service /
  tools/spawn_fab.sh): N daemons, each with a secsgem-py host AND a
  secsgem_client tool, driven by seeded random traffic with end-to-end
  invariant checks (set/get round-trips, event->S6F11 and alarm->S5F1
  delivery, command->tool->completion). Verified green at N=3 (~150 ops/eq,
  all commands round-tripped, 0 violations). Wired into run_interop.sh
  (now 13 steps).

Also fixes the CI break from the previous commit: the Python-client lane's
test_values.py step lacked PYTHONPATH=clients/python (now step-level env).

Two bugs found and fixed while building this, both mine from this batch:
1. carrier test hung on a CancelCarrier of a still-NotConfirmed carrier — a
   self-transition the FSM doesn't signal, so the observer never fired and
   the stream Read blocked forever. Fixed to cancel a Confirmed carrier;
   the NotConfirmed edge is documented as a known E87 limitation.
2. the 500ms stream poll above.

Daemon suite 7 cases / 214 assertions; core 475 / 3097; virtual fab green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 15:05:13 +02:00

136 lines
3.8 KiB
YAML

# All build artifacts live in the named `build` volume (inside Docker), the
# host only provides the source tree mounted read-write at /app.
#
# docker compose build # build the toolchain image
# docker compose run --rm builder # configure + compile
# docker compose run --rm tests # run unit tests
# docker compose up server client # live HSMS demo
x-base: &base
build: .
volumes:
- .:/app
- build:/app/build
services:
builder:
<<: *base
# Put builder on the same network as server/client/equipment_py so
# one-off `docker compose run --rm builder /app/build/secs_interop_probe ...`
# commands can DNS-resolve peer services by their compose name.
networks: [secs]
command:
- bash
- -lc
- >
cmake -S /app -B /app/build -G Ninja -DCMAKE_BUILD_TYPE=Release &&
cmake --build /app/build
tests:
<<: *base
depends_on:
builder:
condition: service_completed_successfully
command: ["ctest", "--test-dir", "/app/build", "--output-on-failure"]
server:
<<: *base
depends_on:
builder:
condition: service_completed_successfully
command: ["/app/build/secs_server", "--port", "5000", "--device", "0"]
networks: [secs]
# No host port publish: the client reaches the server over the `secs`
# Docker network by service name. Uncomment to expose to the host.
# ports:
# - "5000:5000"
client:
<<: *base
depends_on:
builder:
condition: service_completed_successfully
server:
condition: service_started
command: ["/app/build/secs_client", "--host", "server", "--port", "5000", "--device", "0"]
networks: [secs]
# The gRPC daemon: passive HSMS equipment on :5000 plus the gRPC tool API
# on :50051. Used by interop/daemon_interop.py (a gRPC tool + a secsgem-py
# host both drive it) to prove the gRPC<->HSMS bridge against a real host.
gemd:
<<: *base
depends_on:
builder:
condition: service_completed_successfully
command:
- /app/build/secs_gemd
- --port
- "5000"
- --grpc
- "0.0.0.0:50051"
- --config-dir
- /app/data
networks: [secs]
# A virtual fab: FAB_N secs_gemd equipment in one container (HSMS 5100+i,
# gRPC 51000+i). interop/virtual_fab.py attaches a secsgem-py host AND a
# secsgem_client tool to every one and drives seeded random traffic.
fab:
<<: *base
depends_on:
builder:
condition: service_completed_successfully
environment:
FAB_N: "${FAB_N:-3}"
command: ["bash", "tools/spawn_fab.sh"]
networks: [secs]
# Python container preloaded with secsgem-py 0.3.0 for cross-validation
# of our C++ HSMS/SECS-II/GEM implementation against the reference library.
interop:
build: ./interop
volumes:
- .:/app
working_dir: /app/interop
networks: [secs]
# secsgem-py running as passive equipment so the C++ active host can
# connect to it (drive with `secs_interop_probe --host equipment_py`).
equipment_py:
build: ./interop
volumes:
- .:/app
working_dir: /app/interop
command: ["python3", "/app/interop/passive_equipment.py", "--port", "5000"]
networks: [secs]
# Server variant for the persistent-spool restart test. Identical to
# `server` but with --spool-dir pointed at a named volume so spooled
# messages survive `docker compose restart server-spool`.
server-spool:
<<: *base
depends_on:
builder:
condition: service_completed_successfully
command:
- /app/build/secs_server
- --port
- "5000"
- --device
- "0"
- --spool-dir
- /spool
volumes:
- .:/app
- build:/app/build
- spool:/spool
networks: [secs]
networks:
secs: {}
volumes:
build: {}
spool: {}