完善企微开通与部署端口契约(07-26-internal-ops-recovery-baseline)
这个提交包含在:
@@ -517,6 +517,81 @@ bash scripts/qa/run-local-candidate.sh full
|
||||
# Only this successful branch emits LOCAL_CANDIDATE_PASS.
|
||||
```
|
||||
|
||||
## Scenario: Compose-Resolved Loopback Host Port
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: changing Compose port publication, deployment health checks, Caddy
|
||||
routing or operator commands for the server container.
|
||||
- Purpose: support a non-default host port without changing the container's
|
||||
fixed application port or accidentally checking a stale service.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Env: `APP_HOST_PORT=<1-65535>`; default `3000`.
|
||||
- Compose mapping: `127.0.0.1:${APP_HOST_PORT:-3000}:3000`.
|
||||
- Runtime resolution: `docker compose port server 3000` or the compatible
|
||||
`docker-compose` command selected by the deployment script.
|
||||
- Backend container still listens on `PORT=3000`.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- Publication remains loopback-only. `APP_HOST_PORT` changes only the host side;
|
||||
Caddy points to the same resolved host port.
|
||||
- Deployment health checks must parse the running Compose mapping after startup;
|
||||
do not hardcode 3000 and do not assume `.env` variables entered the current
|
||||
shell merely because Compose read `--env-file`.
|
||||
- The parsed port must be numeric before it is used in curl. A missing,
|
||||
multi-line or non-numeric mapping stops deployment before readiness success.
|
||||
- `server`, `worker` and `init` retain the shared env-file contract; only server
|
||||
publishes an HTTP host port.
|
||||
- Changing the real host port or Caddy target is production configuration and
|
||||
requires separate approval plus rollback evidence.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Missing `APP_HOST_PORT` -> Compose uses loopback port 3000.
|
||||
- Valid alternate port -> Compose publishes it and deployment checks that port.
|
||||
- Non-numeric/unresolved `compose port` output -> deployment exits non-zero.
|
||||
- Caddy target differs from Compose mapping -> deployment is not accepted even
|
||||
if one local `/ready` endpoint answers.
|
||||
- Host port already occupied -> Compose startup fails; do not start a second
|
||||
deployment on a guessed port.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: `.env` selects an alternate loopback port, Compose reports it, deploy
|
||||
checks the matching build SHA there and Caddy targets the same port.
|
||||
- Base: default port 3000 remains compatible without extra configuration.
|
||||
- Bad: Compose publishes 3001 while deploy/runbook curls 3000 and reports an old
|
||||
container as the new release.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- `bash -n deploy/trialrun/deploy.sh deploy/trialrun/setup-server.sh`.
|
||||
- `node deploy/trialrun/validate-env-contract.js` must assert the exact
|
||||
loopback-only variable mapping and env example key.
|
||||
- Review runbook/checklist commands for Compose resolution rather than shell
|
||||
expansion of an unloaded `.env`.
|
||||
- Public deployment additionally proves Caddy, `/ready` and frontend marker use
|
||||
the approved build on the same resolved port.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```bash
|
||||
curl -fsS http://127.0.0.1:3000/ready
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```bash
|
||||
SERVER_HOST_PORT="$(docker compose port server 3000)"
|
||||
SERVER_HOST_PORT="${SERVER_HOST_PORT##*:}"
|
||||
curl -fsS "http://127.0.0.1:${SERVER_HOST_PORT}/ready"
|
||||
```
|
||||
|
||||
## Scenario: Paired Backup And Shadow-Only Restore
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
@@ -142,3 +142,93 @@ WeCom leave(category=调休) -> WeCom approval -> OA minimum read-only history
|
||||
- Full-stack Playwright coverage that writes local data still requires a running
|
||||
local backend/API environment; a Vite-only run is not a valid failure signal
|
||||
for those real-chain tests.
|
||||
|
||||
## Scenario: One-Time Private-Info OAuth Onboarding
|
||||
|
||||
### 1. Scope / Trigger
|
||||
|
||||
- Trigger: obtaining phone/email for an app-visible member or allowing an
|
||||
unbound active member to create the minimum OA account.
|
||||
- This is a one-time onboarding path, not the ordinary WeCom login path and not
|
||||
a replacement for administrator-assigned finance, CEO or admin roles.
|
||||
|
||||
### 2. Signatures
|
||||
|
||||
- Entry: `GET /api/auth/wecom/login?mode=onboard&returnTo=<internal-path>`.
|
||||
- Ordinary entry: the same route with `mode=login|bind`.
|
||||
- Provider chain: `snsapi_privateinfo` -> `/cgi-bin/auth/getuserinfo` ->
|
||||
`/cgi-bin/auth/getuserdetail` with `{ "user_ticket": "<short-lived>" }`.
|
||||
- Config: `WECOM_OAUTH_ONBOARDING_ENABLED=false` by default.
|
||||
- Directory chain: `WECOM_CONTACT_SECRET` only for `/user/list_id`;
|
||||
`WECOM_AGENT_SECRET` for `/user/get`, `/department/list` and
|
||||
`/hr/get_staff_info`.
|
||||
|
||||
### 3. Contracts
|
||||
|
||||
- `login|bind` always keeps `snsapi_base`; only explicit `onboard` uses
|
||||
`snsapi_privateinfo`.
|
||||
- The member must be visible to the app, active, return the same userid from
|
||||
both identity calls, and authorize a valid phone plus personal email or
|
||||
business email. A blank/invalid personal email may fall back to a valid
|
||||
business email.
|
||||
- `user_ticket`, OAuth code, access token and raw provider responses exist only
|
||||
in request memory. Do not persist them or include them in Cookie, audit,
|
||||
response details or logs; provider/network failures use fixed safe codes.
|
||||
- Resolve userid, phone and email conflicts before writing. A member already
|
||||
bound to another employee or an employee bound to another member is rejected.
|
||||
- New accounts are created transactionally with employee, exactly the
|
||||
`employee` role, identity binding and dehydrated audit. Existing active
|
||||
accounts may complete normalized profile data and binding but keep all roles.
|
||||
- The feature flag controls the entry and callback provisioning path. Enabling
|
||||
it and changing real app permissions are production configuration operations
|
||||
requiring separate approval.
|
||||
|
||||
### 4. Validation & Error Matrix
|
||||
|
||||
- Flag disabled -> entry is `404`; no private scope or account write.
|
||||
- Missing `user_ticket` -> safe `consent_required` redirect; no write.
|
||||
- Provider/network failure -> `wecom_api_failed` or safe auth error; no raw
|
||||
payload, token or ticket in output/logs.
|
||||
- Userid mismatch -> `identity_mismatch`; inactive member -> `member_inactive`.
|
||||
- Missing/invalid phone, both emails invalid, or unresolved department ->
|
||||
`missing_required_field`.
|
||||
- Multiple employees, unique-field race or cross-binding -> `employee_conflict`.
|
||||
- Department binding points to missing/archived data -> `department_invalid`;
|
||||
multiple same-name OA departments -> `department_ambiguous`.
|
||||
|
||||
### 5. Good/Base/Bad Cases
|
||||
|
||||
- Good: an unbound active member explicitly consents, receives an employee-only
|
||||
account and authenticated session, then an administrator separately assigns
|
||||
any business role with existing audit/session-revocation semantics.
|
||||
- Base: a bound active employee repeats onboarding to complete normalized
|
||||
profile data; no role is added or removed.
|
||||
- Bad: change ordinary login to `snsapi_privateinfo`, use batch `/user/get`
|
||||
absence as proof that email is unavailable, or auto-grant `finance`/`ceo`.
|
||||
|
||||
### 6. Tests Required
|
||||
|
||||
- Route tests for default-off, scope separation, missing ticket, safe provider
|
||||
failure, successful session and employee-only provisioning.
|
||||
- Service tests for active state, userid match, personal-to-business email
|
||||
fallback, invalid fields, multiple matches, cross-binding and existing-role
|
||||
preservation.
|
||||
- Assertions must prove ticket, token, raw payload, phone and email are absent
|
||||
from audit/error serialization; run full backend tests and Prisma validation.
|
||||
|
||||
### 7. Wrong vs Correct
|
||||
|
||||
#### Wrong
|
||||
|
||||
```text
|
||||
ordinary login -> snsapi_privateinfo -> batch sync guesses missing fields
|
||||
-> auto-grant finance/ceo
|
||||
```
|
||||
|
||||
#### Correct
|
||||
|
||||
```text
|
||||
ordinary login -> snsapi_base
|
||||
one-time onboard -> explicit private consent -> safe unique employee-only write
|
||||
business role assignment -> separate administrator-audited operation
|
||||
```
|
||||
|
||||
在新工单中引用
屏蔽一个用户