refactor: simplify production deployment layout

This commit is contained in:
2026-09-23 08:43:09 +08:00
parent 2c20bdcdb8
commit dd24fd9a1c
22 changed files with 160 additions and 222 deletions
+37 -136
View File
@@ -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.
+4 -5
View File
@@ -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"
}
]
-15
View File
@@ -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
View File
@@ -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
+9 -20
View File
@@ -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
+28 -13
View File
@@ -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.
+27
View File
@@ -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
View File
@@ -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`不覆盖新修订。
+1 -1
View File
@@ -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)
+43 -3
View File
@@ -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