refactor: simplify production deployment layout
This commit is contained in:
+37
-136
@@ -1,156 +1,57 @@
|
||||
# Physical-host deployment
|
||||
# Deployment
|
||||
|
||||
Production services run directly on Debian 13 (Trixie) physical/virtual host
|
||||
processes managed by systemd. Docker is permitted only for disposable local or
|
||||
ECS smoke validation; it is not a production runtime dependency.
|
||||
## Production
|
||||
|
||||
The pinned baseline is [`versions.lock.json`](versions.lock.json): Go 1.27.1,
|
||||
`0.1.0-p1.20260919`, Debian 13 amd64 and Asterisk 22.10.1. The Go release
|
||||
package contains only our SIP Agent/Dispatcher business binaries and their
|
||||
systemd units. SaaS-provided MQ, OSS, AI and other infrastructure are endpoints,
|
||||
not packages deployed by this project; local chain validation may use isolated
|
||||
fixtures/mocks only. Secrets, certificates, SIP credentials, broker URLs and
|
||||
phone-log keys are injected separately.
|
||||
Production runs directly on Debian 13 with systemd. The only services deployed
|
||||
by this project are:
|
||||
|
||||
Before every real outbound attempt, obtain a fresh user confirmation in the
|
||||
current conversation and display the exact SIP channel, raw target number and
|
||||
packet-capture plan. Real SIP outbound calls are permitted only from 09:00
|
||||
(inclusive) through 20:00 (exclusive), Asia/Shanghai time; outside that window
|
||||
the Agent/Dispatcher must fail closed rather than wait, retry, delay or switch
|
||||
trunks. A prior confirmation does not authorize retries or another target; stop
|
||||
after a failed attempt until a new confirmation is received.
|
||||
- `sip-go-agent-dispatcher.service`
|
||||
- `sip-go-agent-agent.service`
|
||||
- the separately managed native `asterisk.service`
|
||||
|
||||
## One deployment directory
|
||||
RabbitMQ, OSS, AI providers and SaaS are external endpoints. They are not
|
||||
installed by the production package and are not started by systemd or Docker.
|
||||
|
||||
`deploys/` is the only deployment directory for local checks, physical-host
|
||||
packages, Asterisk installation, configuration examples and systemd services.
|
||||
There is no separate draft service set or compatibility deployment directory.
|
||||
|
||||
## Local and isolated checks
|
||||
|
||||
From the project root:
|
||||
|
||||
```sh
|
||||
make check
|
||||
make release
|
||||
./dist/sip-go-agent agent --help
|
||||
./dist/sip-go-agent dispatcher --help
|
||||
```
|
||||
|
||||
`make release` creates a local-development binary, module copies, SHA-256
|
||||
checksums and a manifest. A dirty-source marker is preserved; a local build is
|
||||
not a signed production candidate or proof of external service acceptance.
|
||||
|
||||
For an isolated Dispatcher-to-Agent startup check, prepare the strict Dispatcher
|
||||
JSON configuration from `config/dispatcher.json.example`, inject its referenced
|
||||
credentials outside the repository, and supply the deployment-owned endpoint
|
||||
inventory and mTLS files. The local acceptance script also requires a loopback
|
||||
RabbitMQ URL because `dispatcher --once` must prove it can publish through the
|
||||
configured broker; it fails closed when the URL is absent:
|
||||
|
||||
```sh
|
||||
GO_SIP_LOCAL_MQ_URL=amqp://guest:guest@127.0.0.1:33252/ \
|
||||
./scripts/acceptance-local.sh
|
||||
```
|
||||
|
||||
The command below is the long-running endpoint check:
|
||||
|
||||
```sh
|
||||
SIP_GO_AGENT_MODE=mock \
|
||||
DISPATCHER_DB=./dispatcher.db \
|
||||
DISPATCHER_AGENT_ENDPOINTS_FILE=./deploys/config/agent-endpoints.example.json \
|
||||
MTLS_CA_FILE=/path/to/ca.pem \
|
||||
MTLS_CERT_FILE=/path/to/dispatcher.pem \
|
||||
MTLS_KEY_FILE=/path/to/dispatcher.key \
|
||||
./dist/sip-go-agent dispatcher --config /path/to/dispatcher.json --once
|
||||
```
|
||||
|
||||
The Dispatcher probes the configured Agent, verifies its boot identity and
|
||||
activates a session before use. Tenant commands cannot select an endpoint or
|
||||
certificate. Agent RPC listening uses `AGENT_GRPC_LISTEN` and deployment-provided
|
||||
mTLS files; an Agent-side peer allowlist can be supplied with
|
||||
`MTLS_PEER_CERT_FINGERPRINTS`. Never place credentials or private keys in this
|
||||
repository.
|
||||
|
||||
Dispatcher owns its SQLite database; Agent owns a separate `AGENT_SPOOL` and has
|
||||
no business database. These are single-node, single-Agent/Cell/tenant checks,
|
||||
not multi-Cell or production acceptance. Local protocol tests do not replace
|
||||
the mandatory deployment/capture diagnostics below. Mock is not authorization
|
||||
for real calls or paid provider requests.
|
||||
|
||||
## Build an uploadable package
|
||||
|
||||
From the project root:
|
||||
Build a release candidate from the project root:
|
||||
|
||||
```sh
|
||||
deploys/build-package.sh
|
||||
deploys/packages/sip-go-agent-0.1.0-p1.20260919-linux-amd64.tar.gz
|
||||
# output: dist/packages/sip-go-agent-<version>-linux-amd64.tar.gz
|
||||
```
|
||||
|
||||
The package includes its SHA-256 manifest, a non-root systemd deployment
|
||||
layout, and the fail-closed non-production capture-first entrypoint. The
|
||||
installer installs that entrypoint as `/usr/local/sbin/agent-call-nonprod-evidence`
|
||||
and adds a dedicated validated sudoers rule for `rogee`; it does not install
|
||||
`tcpdump` or silently weaken production gates. A dirty/unapproved source
|
||||
manifest is intentionally rejected by the installer unless
|
||||
`--allow-nonproduction` is supplied for smoke work.
|
||||
|
||||
## Install on Debian 13
|
||||
|
||||
Upload and extract the archive on the target host, then run as root:
|
||||
The local builder intentionally marks the manifest as not production-approved.
|
||||
Only an externally approved, clean release (`source_dirty=false` and
|
||||
`production_approval=true`) may be installed on a Debian 13 amd64 host as
|
||||
root:
|
||||
|
||||
```sh
|
||||
tar -xzf sip-go-agent-0.1.0-p1.20260919-linux-amd64.tar.gz
|
||||
tar -xzf sip-go-agent-<version>-linux-amd64.tar.gz
|
||||
./install.sh
|
||||
```
|
||||
|
||||
The installer verifies Debian 13 amd64, package checksums and the release
|
||||
manifest; creates `rogee`, `/opt/sip-go-agent`, `/etc/sip-go-agent` and
|
||||
`/var/lib/sip-go-agent`, installs both systemd units, and does not overwrite
|
||||
existing environment or PKI files. Configure the injected values and approved
|
||||
static Cell artifact, then start explicitly:
|
||||
The package contains only the two Go services, their systemd units, production
|
||||
environment templates, the endpoint inventory, the version lock and the
|
||||
installer. Credentials, certificates, broker URLs and the approved static Cell
|
||||
artifact are injected separately. Asterisk is built or installed with the
|
||||
scripts under [`cell/`](cell/), and its management-owned configuration is never
|
||||
overwritten.
|
||||
|
||||
```sh
|
||||
systemctl enable sip-go-agent-agent.service sip-go-agent-dispatcher.service
|
||||
systemctl start sip-go-agent-dispatcher.service sip-go-agent-agent.service
|
||||
```
|
||||
The production install does not install test scripts, test fixtures, Docker or
|
||||
sudo rules for test tooling.
|
||||
|
||||
Use `./install.sh --start` only after the environment, mTLS identity, broker
|
||||
ACL and static Cell artifact have been reviewed. Production mode never silently
|
||||
falls back to Mock.
|
||||
## Test-only tooling
|
||||
|
||||
## Non-production capture-first gate
|
||||
Test dependencies must run in disposable Docker containers. The existing
|
||||
`make mq-integration-local` target starts a temporary RabbitMQ container,
|
||||
runs the integration tests, and removes the container. Do not install RabbitMQ
|
||||
or other test infrastructure as a host service.
|
||||
|
||||
Development, `mock`, `mixed` and non-production `real` validation must use
|
||||
[`cell/nonprod-call-evidence.sh`](cell/nonprod-call-evidence.sh) as the single
|
||||
capture-first entrypoint. It refuses `production`, validates the approved trunk
|
||||
and whitelist target, verifies `tcpdump` raw-capture capability, records the
|
||||
pre-call Debian/systemd/ECS-facing facts plus Asterisk/PJSIP/channel/media
|
||||
state, enables the PJSIP logger, captures SIP UDP 5060 and RTP UDP
|
||||
10000-10800 before the call command starts, and always stops capture/logger and
|
||||
writes redacted status plus SHA-256 facts on success or failure. Real calls reserve a daily attempt in `/var/lib/sip-go-agent/state/real-call-attempts.tsv` per `trunk + original target`; the fourth attempt is rejected fail-closed, while `--preflight-only` does not consume quota. Existing real evidence is counted when seeding the ledger.
|
||||
[`test/nonprod-call-evidence.sh`](test/nonprod-call-evidence.sh) is a host-side
|
||||
capture-first gate for non-production mixed/real calls against native Asterisk.
|
||||
It is deliberately outside the production package and is not a business
|
||||
service. It still fails closed when root, `tcpdump`, Asterisk or the required
|
||||
system diagnostics are unavailable.
|
||||
|
||||
Run it only after a fresh current-conversation confirmation naming the exact
|
||||
trunk, raw target and capture plan; the command after `--` must execute as the
|
||||
non-root `rogee` user. Do not invoke the Agent directly for a non-production
|
||||
real/mixed call, do not retry inside the wrapper, and do not treat a missing
|
||||
PCAP or state snapshot as a pass. The private call output and raw capture stay
|
||||
under the mode-0700 evidence directory and must not be copied into repository
|
||||
long-term evidence without redaction.
|
||||
|
||||
## Cell boundary
|
||||
|
||||
Asterisk remains the SIP owner and is installed as the separately approved
|
||||
physical Asterisk 22.10.1 Cell service. Asterisk and the SIP Agent are the only
|
||||
business-code services in this repository. The Go package does not rewrite
|
||||
`pjsip.conf`, embed SIP credentials, or run Asterisk in Docker. Management owns
|
||||
the immutable static Cell artifact and its systemd/maintenance release; the Go
|
||||
Agent consumes the approved artifact and reports the applied revision.
|
||||
|
||||
RabbitMQ, OSS, AI providers and SaaS APIs are external infrastructure. They are
|
||||
not installed by `deploys/`; their production ACLs/endpoints are supplied by
|
||||
SaaS, while local validation uses explicitly isolated test infrastructure. For
|
||||
non-ECS/offline Alibaba OSS validation, use
|
||||
`deploys/env/dispatcher.offline-oss.env.example` (public
|
||||
`oss-cn-beijing.aliyuncs.com`); keep `dispatcher.env.example`'s internal
|
||||
endpoint for the separately managed production ECS profile.
|
||||
The test directory also contains the offline OSS environment example and the
|
||||
AI fixture. They are test inputs only; they do not authorize external access
|
||||
and are never copied into a production release.
|
||||
|
||||
@@ -8,19 +8,18 @@ VERSION=${1:-$(awk -F'"' '/"version"[[:space:]]*:/ {print $4; exit}' "$LOCK")}
|
||||
|
||||
RELEASE_DIR="$ROOT/dist/release-$VERSION"
|
||||
STAGE="$ROOT/dist/package-$VERSION"
|
||||
ARCHIVE="$ROOT/deploys/packages/sip-go-agent-$VERSION-linux-amd64.tar.gz"
|
||||
ARCHIVE="$ROOT/dist/packages/sip-go-agent-$VERSION-linux-amd64.tar.gz"
|
||||
rm -rf -- "$RELEASE_DIR" "$STAGE" "$ARCHIVE" "$ARCHIVE.sha256"
|
||||
RELEASE_VERSION="$VERSION" "$ROOT/scripts/build-release.sh" "$RELEASE_DIR"
|
||||
mkdir -p "$STAGE" "$(dirname -- "$ARCHIVE")"
|
||||
cp -a "$RELEASE_DIR/." "$STAGE/"
|
||||
mkdir -p "$STAGE/systemd" "$STAGE/env" "$STAGE/config" "$STAGE/cell"
|
||||
mkdir -p "$STAGE/systemd" "$STAGE/env" "$STAGE/config"
|
||||
cp "$ROOT/deploys/install.sh" "$STAGE/install.sh"
|
||||
cp "$ROOT/deploys/cell/nonprod-call-evidence.sh" "$STAGE/cell/nonprod-call-evidence.sh"
|
||||
cp "$ROOT/deploys/systemd/"*.service "$STAGE/systemd/"
|
||||
cp "$ROOT/deploys/env/"*.env.example "$STAGE/env/"
|
||||
cp "$ROOT/deploys/env/agent.env.example" "$ROOT/deploys/env/dispatcher.env.example" "$STAGE/env/"
|
||||
cp "$ROOT/deploys/config/agent-endpoints.example.json" "$STAGE/config/"
|
||||
cp "$LOCK" "$STAGE/versions.lock.json"
|
||||
chmod 0755 "$STAGE/install.sh" "$STAGE/cell/nonprod-call-evidence.sh"
|
||||
chmod 0755 "$STAGE/install.sh"
|
||||
chmod 0644 "$STAGE/systemd/"*.service "$STAGE/env/"*.env.example "$STAGE/config/agent-endpoints.example.json" "$STAGE/versions.lock.json"
|
||||
(
|
||||
cd "$STAGE"
|
||||
|
||||
@@ -4,11 +4,5 @@
|
||||
"cell_id": "cell-a",
|
||||
"address": "agent-cell-a.internal:19090",
|
||||
"server_name": "agent-cell-a.internal"
|
||||
},
|
||||
{
|
||||
"agent_id": "agent-cell-b",
|
||||
"cell_id": "cell-b",
|
||||
"address": "agent-cell-b.internal:19090",
|
||||
"server_name": "agent-cell-b.internal"
|
||||
}
|
||||
]
|
||||
|
||||
Vendored
-15
@@ -16,18 +16,3 @@ DISPATCHER_AGENT_ENDPOINTS_FILE=/etc/sip-go-agent/agent-endpoints.json
|
||||
AGENT_STATIC_ARTIFACT=/etc/sip-go-agent/artifacts/cell-a.json
|
||||
AGENT_CALL_BUSINESS_LOG=/var/lib/sip-go-agent/agent/call-business.jsonl
|
||||
# AGENT_CALL_PHONE_LOG_KEY=<injected-secret-at-least-16-bytes>
|
||||
# Optional one-shot flow inputs; mode selects adapters, not a separate business path.
|
||||
# AGENT_CALL_TARGET=<raw-approved-target-number>
|
||||
# AGENT_CALL_TRUNK_ID=<enabled-trunk-id>
|
||||
# AGENT_CALL_CALLER_ID=<approved-caller-profile>
|
||||
# AGENT_CALL_MEDIA_BIND=127.0.0.1
|
||||
# AGENT_CALL_MEDIA_PORT=12000
|
||||
# AGENT_CALL_RECORDING_DIR=/var/lib/sip-go-agent/recordings
|
||||
# AGENT_CALL_AI_SNAPSHOT=/etc/sip-go-agent/ai/full-ai-v1.json
|
||||
# Required only when DISPATCHER_GRPC_ENDPOINT is enabled for --call-once evidence:
|
||||
# AGENT_CALL_TENANT_ID=<approved-tenant-id>
|
||||
# AGENT_CALL_TENANT_KEY=<approved-tenant-key>
|
||||
# AGENT_CALL_TASK_ID=<approved-task-id>
|
||||
# AGENT_CALL_TASK_ITEM_ID=<approved-task-item-id>
|
||||
RABBITMQ_EXCHANGE=agent-call.commands.v1
|
||||
# RABBITMQ_URL=<injected-broker-url>
|
||||
|
||||
+3
-14
@@ -6,11 +6,9 @@ if [[ ${EUID} -ne 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ALLOW_NONPRODUCTION=false
|
||||
START=false
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--allow-nonproduction) ALLOW_NONPRODUCTION=true ;;
|
||||
--start) START=true ;;
|
||||
*) echo "unknown option: $arg" >&2; exit 2 ;;
|
||||
esac
|
||||
@@ -30,10 +28,8 @@ sha256sum -c package.SHA256SUMS
|
||||
|
||||
if ! grep -q '"source_dirty": false' manifest.json ||
|
||||
! grep -q '"production_approval": true' manifest.json; then
|
||||
if [[ "$ALLOW_NONPRODUCTION" != true ]]; then
|
||||
echo 'release is dirty or not production-approved; use an approved package or --allow-nonproduction for smoke only' >&2
|
||||
exit 1
|
||||
fi
|
||||
echo 'release is dirty or not production-approved' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION=$(awk -F'"' '/"version"[[:space:]]*:/ {print $4; exit}' manifest.json)
|
||||
@@ -47,14 +43,6 @@ install -d -m 0755 /opt/sip-go-agent/releases \
|
||||
install -d -m 0700 -o rogee -g rogee \
|
||||
/var/lib/sip-go-agent/agent /var/lib/sip-go-agent/agent/spool \
|
||||
/var/lib/sip-go-agent/dispatcher
|
||||
install -m 0755 cell/nonprod-call-evidence.sh /usr/local/sbin/agent-call-nonprod-evidence
|
||||
command -v visudo >/dev/null || { echo 'visudo is required for the non-production capture gate' >&2; exit 1; }
|
||||
cat >/etc/sudoers.d/agent-call-nonprod-evidence <<'EOF'
|
||||
rogee ALL=(root) NOPASSWD: /usr/local/sbin/agent-call-nonprod-evidence
|
||||
EOF
|
||||
chmod 0440 /etc/sudoers.d/agent-call-nonprod-evidence
|
||||
visudo -cf /etc/sudoers.d/agent-call-nonprod-evidence >/dev/null
|
||||
|
||||
RELEASE_DIR=/opt/sip-go-agent/releases/$VERSION
|
||||
install -d -m 0755 "$RELEASE_DIR"
|
||||
install -m 0755 sip-go-agent "$RELEASE_DIR/sip-go-agent"
|
||||
@@ -74,6 +62,7 @@ if [[ ! -e /etc/sip-go-agent/agent-endpoints.json ]]; then
|
||||
install -m 0644 config/agent-endpoints.example.json /etc/sip-go-agent/agent-endpoints.json
|
||||
fi
|
||||
install -m 0644 versions.lock.json /etc/sip-go-agent/versions.lock.json
|
||||
rm -f /usr/local/sbin/agent-call-nonprod-evidence /etc/sudoers.d/agent-call-nonprod-evidence
|
||||
systemctl daemon-reload
|
||||
systemctl enable sip-go-agent-agent.service sip-go-agent-dispatcher.service
|
||||
|
||||
|
||||
@@ -1,23 +1,12 @@
|
||||
# Release packages
|
||||
# Asterisk production inputs
|
||||
|
||||
`../build-package.sh` writes the self-contained Linux package and checksum here:
|
||||
This directory contains only the locked Asterisk source/dependency archives and
|
||||
native stage used by the physical Cell deployment:
|
||||
|
||||
```text
|
||||
sip-go-agent-<locked-version>-linux-amd64.tar.gz
|
||||
sip-go-agent-<locked-version>-linux-amd64.tar.gz.sha256
|
||||
```
|
||||
- `asterisk-22.10.1-source.tar.gz` and its checksum
|
||||
- `asterisk-22.10.1-deps/`
|
||||
- `asterisk-22.10.1-native/`
|
||||
|
||||
The package contains only the SIP Agent/Dispatcher business binary, module
|
||||
checksums, manifest, systemd units, safe environment templates, endpoint
|
||||
template, version lock and `install.sh`. It contains no credentials,
|
||||
certificates, phone log key, audio, provider configuration, MQ/OSS/AI runtime or
|
||||
SaaS infrastructure.
|
||||
|
||||
Asterisk 22.10.1 source, its third-party dependency cache and a validated
|
||||
native stage are staged separately under `asterisk-22.10.1-*` and
|
||||
`asterisk-22.10.1-native/`, with SHA-256 files and a locked Git commit. A management-approved physical Cell release can
|
||||
use `deploys/cell/build-asterisk-native.sh` or the native stage, then must
|
||||
own its systemd unit/config;
|
||||
this Go package does not rewrite Asterisk. RabbitMQ, OSS and AI are SaaS
|
||||
infrastructure endpoints rather than packages here; this directory does not
|
||||
turn any of them into Docker services.
|
||||
Build or install them with the scripts in `../cell/`. The Go Agent/Dispatcher
|
||||
release is written to `../../dist/packages/`; RabbitMQ, OSS and AI are not
|
||||
installed here.
|
||||
@@ -1 +0,0 @@
|
||||
fd38deece346f1f8ef820a465d41046ccf04ffa2a67c0791dba452a6234b00e5 deploys/packages/sip-go-agent-0.1.0-p1.20260919-linux-amd64.tar.gz
|
||||
@@ -1,16 +1,31 @@
|
||||
# Production physical deployment contract
|
||||
# Physical production deployment
|
||||
|
||||
This project installs the Go Agent and single-active Dispatcher as ordinary
|
||||
Debian 13 systemd services. The production path is not a Docker Compose stack.
|
||||
Production is a small systemd installation on Debian 13 amd64:
|
||||
|
||||
- Version and host pins: `versions.lock.json`.
|
||||
- Uploadable package: `packages/` after `build-package.sh`.
|
||||
- Service units: `systemd/`.
|
||||
- Safe configuration templates: `env/` and `config/`.
|
||||
- Secrets/PKI/static Cell artifacts: injected by deployment, never packaged.
|
||||
- Asterisk 22.10.1: separate approved physical Cell installation; this project
|
||||
does not rewrite or containerize it.
|
||||
1. native Asterisk Cell (`asterisk.service`), owned by the approved SIP
|
||||
management release;
|
||||
2. `sip-go-agent-agent.service`;
|
||||
3. `sip-go-agent-dispatcher.service`.
|
||||
|
||||
The package installer deliberately refuses dirty or non-approved manifests for
|
||||
production. `--allow-nonproduction` exists only for an explicitly labelled
|
||||
smoke installation and does not change the manifest or claim P1 acceptance.
|
||||
This project does not run production services in Docker and does not install
|
||||
RabbitMQ, OSS, AI or SaaS infrastructure. Those systems are supplied through
|
||||
injected endpoints and credentials.
|
||||
|
||||
The pinned versions are in [`versions.lock.json`](versions.lock.json). Build
|
||||
a release candidate with `build-package.sh`; it writes the archive to
|
||||
`dist/packages/` and is intentionally not production-approved. Production
|
||||
installation requires a clean, externally approved manifest. Build/install
|
||||
Asterisk separately with the scripts in [`cell/`](cell/). The Asterisk
|
||||
installer preserves `/etc/asterisk`; the management-approved static Cell
|
||||
configuration is installed separately.
|
||||
|
||||
The Go installer creates `/opt/sip-go-agent`, `/etc/sip-go-agent` and
|
||||
`/var/lib/sip-go-agent`, installs the two Go units, and enables them. It does
|
||||
not install test tooling, Docker, a broker, provider SDK credentials or AI
|
||||
snapshots. Start services only after the injected environment, mTLS identity,
|
||||
broker ACL and static Cell artifact have been reviewed.
|
||||
|
||||
For local or isolated testing, use the Docker-backed RabbitMQ target
|
||||
`make mq-integration-local`. Use [`test/nonprod-call-evidence.sh`](test/nonprod-call-evidence.sh)
|
||||
for the required capture-first gate when testing against a native non-production
|
||||
Asterisk host. Neither path is part of the production package.
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# Test-only deployment helpers
|
||||
|
||||
Nothing in this directory is installed by the production package.
|
||||
|
||||
## Dependency infrastructure
|
||||
|
||||
Use the existing Docker-backed target for RabbitMQ integration tests:
|
||||
|
||||
```sh
|
||||
make mq-integration-local
|
||||
```
|
||||
|
||||
It starts a disposable RabbitMQ container, waits for readiness, runs the
|
||||
integration tests and removes the container. Do not install RabbitMQ as a
|
||||
systemd service or add it to a production host.
|
||||
|
||||
## Native Asterisk validation
|
||||
|
||||
`nonprod-call-evidence.sh` is the mandatory capture-first wrapper for
|
||||
non-production `mock`, `mixed` and `real` call checks. It runs on a validation
|
||||
host with native Asterisk and required diagnostics; it is not an Asterisk or
|
||||
Agent replacement and is not containerized. Run it explicitly with the current
|
||||
call authorization and the approved target/trunk. It refuses production mode
|
||||
and fails closed when its prerequisites are missing.
|
||||
|
||||
The offline OSS environment file and `ai-dental-meiba-v1.json` are fixtures for
|
||||
isolated tests only. They contain no real credentials or production approval.
|
||||
@@ -54,7 +54,7 @@ The second Cell is intentionally out of this iteration.
|
||||
- The three earlier bounded real-provider CallFlow attempts and their
|
||||
no-`StasisStart` causes remain recorded in
|
||||
`docs/evidence/20260919-real-provider-callflow-attempts.json`.
|
||||
- The new 美吧口腔 policy is in `deploys/config/ai-dental-meiba-v1.json`: it
|
||||
- The new 美吧口腔 policy is in `deploys/test/ai-dental-meiba-v1.json`: it
|
||||
identifies 美吧口腔, asks which dental project the user wants, limits the
|
||||
call to three effective turns/120 seconds, and requires the
|
||||
`[INVALID_CALL]` early-stop marker for invalid calls.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"tcpdump_filter": "udp port 5060 or udp portrange 10000-10800"
|
||||
},
|
||||
"entrypoint": {
|
||||
"path": "deploys/cell/nonprod-call-evidence.sh",
|
||||
"path": "deploys/test/nonprod-call-evidence.sh",
|
||||
"remote_path": "/usr/local/sbin/agent-call-nonprod-evidence",
|
||||
"call_id": "preflight-20260920-v6",
|
||||
"mode": "--preflight-only",
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"pcap_bytes": 24
|
||||
},
|
||||
"preflight": {
|
||||
"entrypoint": "deploys/cell/nonprod-call-evidence.sh",
|
||||
"entrypoint": "deploys/test/nonprod-call-evidence.sh",
|
||||
"call_id": "preflight-20260920-v2",
|
||||
"run_command": "/bin/true",
|
||||
"entrypoint_exit": 0,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 2026-09-20 非 ECS OSS 测试配置
|
||||
|
||||
- 离线/非 ECS OSS 测试使用 `cn-beijing` 的公网 Endpoint:`oss-cn-beijing.aliyuncs.com`。
|
||||
- 可复用示例:`deploys/env/dispatcher.offline-oss.env.example`。
|
||||
- 可复用示例:`deploys/test/dispatcher.offline-oss.env.example`。
|
||||
- 生产 ECS 示例 `deploys/env/dispatcher.env.example` 继续使用受控内网 Endpoint,不与离线配置混用。
|
||||
- AK/SK 仅通过受控运行时文件注入;本证据不保存凭据。
|
||||
- 普通离线 OSS 测试不创建、释放或清理 ECS;ECS 仅在另行授权的真实外呼阶段使用。
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"fixed_eip": "123.56.71.98",
|
||||
"package": "deploys/packages/sip-go-agent-0.1.0-p1.20260919-linux-amd64.tar.gz",
|
||||
"package_sha256": "b189e2cf2d51417fcb5b56a190b1f93ad8fa5e33b6e3f2eabdafb9ac66eaa10a",
|
||||
"ai_snapshot": "deploys/config/ai-dental-meiba-v1.json",
|
||||
"ai_snapshot": "deploys/test/ai-dental-meiba-v1.json",
|
||||
"ai_version_id": "agent_dental_meiba_v1"
|
||||
},
|
||||
"route": {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"capture_filter": "udp port 5060 or udp portrange 10000-10800"
|
||||
},
|
||||
"entrypoint": {
|
||||
"path": "deploys/cell/nonprod-call-evidence.sh",
|
||||
"path": "deploys/test/nonprod-call-evidence.sh",
|
||||
"remote_path": "/usr/local/sbin/agent-call-nonprod-evidence",
|
||||
"capture_first": true,
|
||||
"pjsip_logger_enabled_before_call": true,
|
||||
|
||||
@@ -57,6 +57,6 @@ Dispatcher 配置文件是 OSS 配置唯一来源;grant 固定 15 分钟。Age
|
||||
|
||||
## 未执行与边界
|
||||
|
||||
已按 `deploys/cell/nonprod-call-evidence.sh --preflight-only` 尝试诊断;当前开发主机以非root运行,入口立即以 `must run as root for tcpdump and Asterisk diagnostics` fail-closed(日志 `/tmp/go-sip-nonprod-preflight-missing.log`)。同时核验主机缺少 Asterisk、tcpdump,systemd 为 degraded。因此没有伪造 mixed/real 抓包、真实外呼或部署诊断通过。项目的本地 mock/协议回归通过不替代该诊断,也不替代第二阶段真实供应商/SaaS/生产验收。
|
||||
已按 `deploys/test/nonprod-call-evidence.sh --preflight-only` 尝试诊断;当前开发主机以非root运行,入口立即以 `must run as root for tcpdump and Asterisk diagnostics` fail-closed(日志 `/tmp/go-sip-nonprod-preflight-missing.log`)。同时核验主机缺少 Asterisk、tcpdump,systemd 为 degraded。因此没有伪造 mixed/real 抓包、真实外呼或部署诊断通过。项目的本地 mock/协议回归通过不替代该诊断,也不替代第二阶段真实供应商/SaaS/生产验收。
|
||||
|
||||
本目标明确要求保留工作树自有改动且不自动提交、暂存或清理;因此当前改动保持未提交/未暂存。验收依据是上述可复现命令、日志和源码检查,不是任务树声明或提交状态。最终工作树摘要另保存于 `/tmp/go-sip-working-tree-final.txt`。
|
||||
|
||||
+1
-1
@@ -267,7 +267,7 @@ go build ./...
|
||||
| W05/W08/W12 / 完成 | 旧SaaS HTTP业务入口已删除;MQ查询/补传、控制worker、重复/丢回复/SQLite重启、断连和迟到revision有本地往返证据,见`mq-control-recovery.md`、查询/补传证据及`20260922-mq-only-local-final.md` | 外部SaaS receipt不在本轮;accepted不等于applied,不重发执行当补传 |
|
||||
| W07 / 完成 | 实际本地RabbitMQ配置请求/内嵌授权响应、原请求关联、重复、范围和SQLite恢复通过,见`mq-ai-local-roundtrip.md` | 不发明独立授权消息;Agent动态交付边界仍按现有Unary合同 |
|
||||
| W11 / 完成 | D配置文件、固定15分钟授权、原请求/显式新请求、单次PUT及recording.uploaded恢复已有本地证据,见`20260921-mq-upload-progress.md` | 不等待SaaS verified/OSS ID;不宣称SaaS消费 |
|
||||
| W13/W14 / 完成 | 本地/隔离联合MQ、D1/D2隔离、断连/不可路由/confirm/DLQ/重启/覆盖率和acceptance已通过;`deploys/cell/nonprod-call-evidence.sh --preflight-only`已实际执行并因当前主机缺Asterisk/tcpdump且非root而fail-closed,未将其记为mixed/real通过 | 本地门禁已解除;物理部署诊断具备相应主机条件后另行执行,不得用本地Mock代替mixed/real诊断 |
|
||||
| W13/W14 / 完成 | 本地/隔离联合MQ、D1/D2隔离、断连/不可路由/confirm/DLQ/重启/覆盖率和acceptance已通过;`deploys/test/nonprod-call-evidence.sh --preflight-only`已实际执行并因当前主机缺Asterisk/tcpdump且非root而fail-closed,未将其记为mixed/real通过 | 本地门禁已解除;物理部署诊断具备相应主机条件后另行执行,不得用本地Mock代替mixed/real诊断 |
|
||||
|
||||
当前负责人为本会话Agent,用户明确要求不启动子Agent。实现前基线已完成,见[基线证据](archive/evidence/20260921-mq-only-adjustment-baseline.md);v2方向及AI JCS摘要已获确认,本地v3契约和主要MQ实现已有新增证据。W01/W02/W05/W07/W08/W11/W12的项目内门禁已按§8.2关闭;W04项目内门禁和W13/W14本地/隔离门禁已关闭;外部权威签收及物理部署诊断需具备相应外部条件后另行执行,不属于本地MQ-only目标的完成条件。旧通过数和`docs/archive/evidence/20260920-local-p1-acceptance.md`不覆盖新修订。
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ func TestDeploymentHasOneCanonicalDirectory(t *testing.T) {
|
||||
if _, err := os.Stat(filepath.Join(root, "deploy")); !errors.Is(err, os.ErrNotExist) {
|
||||
t.Fatalf("obsolete deploy directory must not remain: %v", err)
|
||||
}
|
||||
for _, path := range []string{"README.md", "build-package.sh", "install.sh", "versions.lock.json", "cell/install-asterisk-native.sh", "cell/nonprod-call-evidence.sh", "systemd/sip-go-agent-agent.service", "systemd/sip-go-agent-dispatcher.service", "config/dispatcher.json.example"} {
|
||||
for _, path := range []string{"README.md", "build-package.sh", "install.sh", "versions.lock.json", "cell/install-asterisk-native.sh", "test/nonprod-call-evidence.sh", "test/README.md", "systemd/sip-go-agent-agent.service", "systemd/sip-go-agent-dispatcher.service", "config/dispatcher.json.example"} {
|
||||
info, err := os.Stat(filepath.Join(root, "deploys", path))
|
||||
if err != nil {
|
||||
t.Errorf("canonical deployment entry %s: %v", path, err)
|
||||
|
||||
@@ -11,7 +11,9 @@ import (
|
||||
|
||||
"git.ipao.vip/rogee/go-sip/internal/mq"
|
||||
"git.ipao.vip/rogee/go-sip/internal/store"
|
||||
"git.ipao.vip/rogee/go-sip/internal/tenant"
|
||||
"git.ipao.vip/rogee/go-sip/internal/testfixture"
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
)
|
||||
|
||||
func TestLocalDispatcherConsumesCommandIntoSQLiteAndPublishesOutbox(t *testing.T) {
|
||||
@@ -19,7 +21,7 @@ func TestLocalDispatcherConsumesCommandIntoSQLiteAndPublishesOutbox(t *testing.T
|
||||
if url == "" {
|
||||
t.Skip("RABBITMQ_URL is not configured")
|
||||
}
|
||||
broker, err := mq.OpenWithPrefetch(url, "", 1)
|
||||
broker, err := mq.OpenWithPrefetch(url, testfixture.DispatcherID, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -37,6 +39,9 @@ func TestLocalDispatcherConsumesCommandIntoSQLiteAndPublishesOutbox(t *testing.T
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
if err := st.BindDispatcherID(testfixture.DispatcherID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
d, err := New(st, broker, time.Now)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -46,17 +51,52 @@ func TestLocalDispatcherConsumesCommandIntoSQLiteAndPublishesOutbox(t *testing.T
|
||||
t.Fatal(err)
|
||||
}
|
||||
const tenantKey = "tenant-demo-key"
|
||||
routingKey := "agent-call.tenant." + tenantKey + ".call.execute"
|
||||
route, err := tenant.NewDispatcherRoute(testfixture.DispatcherID, tenantKey)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := broker.DeclareTenantQueue(tenantKey); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
routingKey := route.InboundKey
|
||||
ctx, cancel := context.WithTimeout(t.Context(), 20*time.Second)
|
||||
defer cancel()
|
||||
consumeDone := make(chan error, 1)
|
||||
go func() { consumeDone <- d.ConsumeTenant(ctx, broker, tenantKey) }()
|
||||
if err := broker.Publish(ctx, mq.DefaultExchange, routingKey, raw); err != nil {
|
||||
connection, err := amqp.Dial(url)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer connection.Close()
|
||||
publishChannel, err := connection.Channel()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer publishChannel.Close()
|
||||
if err := publishChannel.Confirm(false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
returned := publishChannel.NotifyReturn(make(chan amqp.Return, 1))
|
||||
confirmation, err := publishChannel.PublishWithDeferredConfirmWithContext(ctx, mq.DefaultExchange, routingKey, true, false, amqp.Publishing{
|
||||
ContentType: "application/json",
|
||||
DeliveryMode: amqp.Persistent,
|
||||
Body: raw,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if confirmation == nil {
|
||||
t.Fatal("command publication confirmation unavailable")
|
||||
}
|
||||
acked, err := confirmation.WaitContext(ctx)
|
||||
if err != nil || !acked {
|
||||
t.Fatalf("command publication was not confirmed: %v", err)
|
||||
}
|
||||
select {
|
||||
case result := <-returned:
|
||||
t.Fatalf("command publication returned: code=%d", result.ReplyCode)
|
||||
default:
|
||||
}
|
||||
deadline := time.Now().Add(10 * time.Second)
|
||||
for {
|
||||
var taskCount, outboxCount int
|
||||
|
||||
Reference in New Issue
Block a user