feat: harden support, localize knowledge and add operator availability [skip ci]
Source-only publication of reviewed commit 64e96a7f423673dc29ad56485be9fc79bce8af1c.
Passed in isolation: focused frontend/backend tests, race checks, TypeScript typecheck, lint, Go compilation, Flowgram build, and deterministic SDK build/tests.
Not accepted as a production release: Next.js export/native assembly remains blocked by environment memory limits; browser interaction and MySQL staging acceptance remain unverified. No production server, database/schema, runtime configuration, deployment, release tag, or workflow execution was performed.
Expected source tree: 0a4a7528e9.
This commit is contained in:
78
docs/features/support-availability.md
Normal file
78
docs/features/support-availability.md
Normal file
@@ -0,0 +1,78 @@
|
||||
# Human support availability and calendars
|
||||
|
||||
This change separates platform presence, working availability, account enablement, and team opening hours. It does not move, close, or reassign existing conversations when an operator goes on leave or becomes unavailable.
|
||||
|
||||
## Presence is a live authenticated connection
|
||||
|
||||
Only authenticated employee connections to the dashboard or dashboard-notification WebSocket establish presence. The notification connection is mounted across the dashboard and workbench. Visitor WebSockets, conversation typing/viewing events, enabled account records, login history, and `LastOnlineAt` do not establish operator presence.
|
||||
|
||||
Each connection obtains a server-generated random lease ID. The database stores that ID, user ID, internal login-session ID, and expiration, never the bearer token or client IP. Authenticated application pings or protocol pongs renew the lease at most once every 15 seconds. The lease lasts at most 90 seconds and never outlives the login's absolute expiration. A clean disconnect shortens only that connection's lease to at most 15 seconds; other tabs/devices remain effective. A process crash or lost network is bounded by the 90-second lease, subject to synchronized server clocks. Closed connections cannot renew a lease afterward. Expired rows are pruned by the existing pending-dispatch loop.
|
||||
|
||||
Every eligibility/presence read checks current session expiration/revocation, account status/deletion/type, and current conversation-view permission. A logout or removed privilege therefore does not wait for the lease TTL. Browser-background tabs still count as an open platform session; this is connectivity, not a claim that a person is looking at the screen. The bounded grace/TTL avoids false offline flicker without claiming instantaneous disconnect detection.
|
||||
|
||||
Leases use the shared application database, not a per-process map, and unique connection IDs prevent collisions across replicas. All replicas must share that database and have synchronized clocks. This does not add distributed WebSocket message fanout, which is a separate pre-existing deployment concern.
|
||||
|
||||
## Working state and dated absence
|
||||
|
||||
Existing numeric values remain compatible: Idle/ready = 0 and Busy = 1. New values are Break = 2, Unavailable = 3, and Vacation = 4. Account/profile/team disabled status remains separate.
|
||||
|
||||
Vacation requires explicit-offset RFC3339 start and end timestamps. The interval is half-open: start inclusive, end exclusive. Scheduling vacation captures the current manual state as `returnServiceStatus`; that state remains effective before the start and after the end. Updating an already scheduled vacation preserves that return state. A later manual Ready/Busy/Break/Unavailable choice cancels the interval. No timer is required: all evaluation derives the effective state from the current instant. Raw `serviceStatus` may retain Vacation after the interval as configuration/history; `effectiveServiceStatus` is authoritative for badges, readiness, routing, and effective-status list filters.
|
||||
|
||||
`POST /api/dashboard/support_availability/me` always targets the authenticated user's existing enabled employee operator profile. A supplied `userId` cannot change the target or create operator privileges. Administrative state changes require `agent.update`. Generic profile edits preserve working state so a stale profile form cannot cancel newly scheduled leave.
|
||||
|
||||
`ReceiveOfflineMessage` is retained for database/API compatibility but deprecated and nonfunctional for live routing. It never overrides presence, leave, calendar, enabled-state, or capacity checks. Offline requests stay in the existing pending pools; no new delivery or assignment promise is attached to that old flag.
|
||||
|
||||
## One authoritative calendar per team
|
||||
|
||||
`SupportCalendar` contains one optional row per team. Team ID 0 is deployment-wide timezone configuration only, not an organization and not an implicit schedule. The default for new explicitly enabled recurring calendars is the explicit IANA zone `UTC`; a team can specify another IANA timezone or inherit the deployment default. The Go binary embeds timezone data. Docker's historical `Asia/Shanghai` setting is not evidence of the active server's timezone and is not used to reinterpret saved shifts.
|
||||
|
||||
Modes:
|
||||
|
||||
- `legacy` (shown as dated shifts): the default when a team has no new calendar row. Existing `AgentTeamSchedule` rows are evaluated as the exact stored instants. Their existing process-local input/display convention is unchanged. The response labels the real legacy process zone, which can be `Local`, rather than pretending it is a new IANA setting.
|
||||
- `recurring`: an explicit administrator opt-in. Only the saved weekly rules and date exceptions are used. Existing dated rows are retained but inactive. Switching back to dated-shift mode uses those unchanged rows again. There is no silent conversion, bulk backfill, year-long shift, or schedule merging.
|
||||
|
||||
`deploymentTimezone` always identifies the new configured default independently of the legacy display zone. A legacy-to-recurring editor must use this field, not reinterpret the legacy zone. Preview returns the evaluated mode, timezone, open flag, and an explicit UTC RFC3339 next-opening instant when known.
|
||||
|
||||
Weekly rules use ISO weekdays 1 (Monday) through 7 (Sunday), with one or more HH:mm intervals per day. `24:00` is allowed only as an end boundary. Equal start/end is rejected; end before start denotes an overnight interval. Split shifts are supported. A date exception replaces that entire civil date: `closed` means a holiday/closure, otherwise its intervals replace the weekly intervals. An exception also cuts off a preceding overnight carry at midnight. Overlapping intervals are treated as their union, never duplicate capacity.
|
||||
|
||||
DST is defined, not left to host normalization: a nonexistent boundary advances to the first valid local minute; an ambiguous start uses the earlier occurrence and an ambiguous end the later occurrence. A fold-spanning interval is continuous between those instants. An interval entirely swallowed by a forward jump has no duration and is closed. Tests cover New York hour transitions and Lord Howe half-hour transitions.
|
||||
|
||||
For recurring calendars the next-opening search is bounded to 366 future civil days. A missing `nextOpeningAt` means no opening was found in that horizon, not a guarantee of permanent closure. Dated mode uses the earliest stored future opening. A calendar opening is not a promise that a ready operator will be online then, and no waiting-time estimate is invented.
|
||||
|
||||
## Eligibility, assignment, and public status
|
||||
|
||||
The shared eligibility evaluator is used by automatic candidates, manual assignment/transfer target lists and commits, readiness/simulation, and public status. It checks profile, employee account, team enablement, the authoritative calendar, authenticated presence, effective working state, automatic-assignment opt-in when appropriate, and capacity. Manual assignment does not require auto-assign opt-in, but cannot bypass the other checks or the conversation AI agent's team scope. Empty configured team IDs retain the existing all-enabled-team fallback.
|
||||
|
||||
`maxConcurrentCount = 0` consistently means unlimited, as in the original dispatcher. Negative values remain invalid. Assignment and transfer acquire the target profile's portable database write lock before the transaction's first consistent read, then reload eligibility and active load before committing. SQLite serializes writes; MySQL acquires the target row lock before establishing the repeatable-read snapshot. A conditional conversation update prevents concurrent targets from both winning. Changing availability does not consume or silently relinquish existing conversations.
|
||||
|
||||
The unauthenticated `GET /api/support/status` resolves the enabled channel through the existing channel header and returns only `available`, `reason`, `timezone`, and optional `nextOpeningAt`. It is unrelated to the visitor's own WebSocket connection indicator. No employee identities, absence dates, private absence reasons, team counts, or invented wait times are returned. The endpoint uses the existing per-address public rate-limit middleware (120 requests per configured window).
|
||||
|
||||
Public reason precedence is:
|
||||
|
||||
1. `available`: at least one currently eligible automatically routable human.
|
||||
2. `outside_hours`: no enabled routed team has an open calendar. If known, report the earliest opening and its matching timezone.
|
||||
3. `capacity`: calendars are open and at least one otherwise eligible operator is at capacity.
|
||||
4. `temporarily_absent`: other cases such as disconnected, busy, on leave, unavailable, or no usable human configuration.
|
||||
|
||||
Existing pending team/global pools and the periodic dispatch loop are reused. New live presence, state saves, and calendar saves may trigger another pending scan. SLA engines, a new fair queue, automatic vacation handover, and guaranteed staffing forecasts are not part of this change.
|
||||
|
||||
## API and permissions
|
||||
|
||||
All endpoints retain the shared JSON result envelope.
|
||||
|
||||
- GET/POST `/api/dashboard/support_availability/me`: authenticated staff, own existing profile only; GET returns null when there is no profile.
|
||||
- GET `/api/dashboard/support_availability/agents`: `agent.view`, `conversation.assign`, or `conversation.transfer`. `conversationId` restricts target teams and applies conversation-view/transfer access checks; it is required when the caller lacks `agent.view`.
|
||||
- POST `/api/dashboard/support_availability/agent`: `agent.update`, explicit `userId`.
|
||||
- GET `/api/dashboard/support_availability/calendar?teamId=N`: `agentTeamSchedule.view` for teams; `supportConfig.view` for deployment ID 0.
|
||||
- POST `/api/dashboard/support_availability/calendar`: `agentTeamSchedule.update` for teams; `supportConfig.update` for deployment ID 0.
|
||||
- POST `/api/dashboard/support_availability/calendar/preview`: corresponding read permission, no persistence.
|
||||
|
||||
The state DTO includes both configured/effective state, return state, own/admin absence bounds, presence, eligibility reason, effective calendar, and next calendar opening. Calendar DTOs include weekly/exception structures, the explicit override, effective timezone, and deployment timezone. New enum values are generated from `internal/pkg/enums/agent.go`, not handwritten frontend duplicates.
|
||||
|
||||
## Rollout, schema, and validation
|
||||
|
||||
Startup AutoMigrate registers the new `SupportCalendar` and `AgentPresenceLease` tables and adds `absence_start_at`, `absence_end_at`, `availability_return_status`, and `assignment_version` to agent profiles. Normal SQLite/MySQL GORM mappings and indexes are used; no versioned destructive migration or seeded schedule is needed. Back up the deployment database and review the migration in staging before release. This work has not run a migration, restarted a service, or sent a real customer message on the working server.
|
||||
|
||||
Roll out backend/frontend/replicas together: an old binary would not enforce the new presence and vacation rules. After startup, operators must have a connected authenticated dashboard notification socket before receiving new work. Initially all teams retain dated schedules; configure the deployment timezone and review each team's preview before explicitly enabling recurring mode. Existing conversation owners are preserved. Rolling back to an old binary requires operational care because it will not honor the new eligibility semantics even if added columns/tables remain harmless.
|
||||
|
||||
Validation uses temporary SQLite databases, pure evaluator tests, and httptest requests, never production data. Focused tests cover split/overnight shifts, holidays, explicit opt-in, DST folds/gaps, bounded next openings, multi-tab/device aggregation, expiration/revocation/privilege removal, absence boundaries, capacity guards, channel-scoped privacy, and self/admin authorization. MySQL live concurrency and a real deployed browser/WebSocket liveness scenario still require staging validation; portable locking and SQL have been reviewed but not claimed as production-tested.
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
This targeted change is based on canonical Gitea main snapshot
|
||||
`e57610a27c556e005b9b3b68115477ba1fdd3f1b` (tree
|
||||
`9cf938c2498ce6b63faaad7cf8b286c1cfe7a5ef`). It does not import the separate
|
||||
hardening-review branch.
|
||||
`9cf938c2498ce6b63faaad7cf8b286c1cfe7a5ef`). That original targeted change did not import
|
||||
the separate hardening-review branch. The current integration reconciles it with that branch,
|
||||
preserving both realtime recovery and scoped identity/draft safeguards.
|
||||
|
||||
## Report and source findings
|
||||
|
||||
@@ -61,8 +62,10 @@ socket close events, hidden/unmounted lifecycle, single-flight fallback,
|
||||
identity/view changes, abort deadlines, stalled previous-view fetches, history
|
||||
failures and late close responses.
|
||||
|
||||
**The regression tests have not been executed.** Full project typecheck, lint,
|
||||
build and browser/runtime validation have not been run; permission for tests,
|
||||
installation and runtime verification remains pending. No services were started,
|
||||
real support messages sent, or deployment performed. Do not describe this source
|
||||
patch as a verified production fix.
|
||||
The integrated 2026-10-16 baseline has now executed all 226 frontend Node tests,
|
||||
including these recovery regressions, and the declared-dependency frontend
|
||||
typecheck. Both passed. Focused backend security/setup tests and their race run
|
||||
also passed; see [the current validation record](../hardening/VALIDATION.md) for
|
||||
exact scope, fixture corrections and remaining build/lint/MySQL/browser gates.
|
||||
No production services were started, real support messages sent, or deployment
|
||||
performed. These checks are not a claim of production acceptance.
|
||||
|
||||
109
docs/hardening/CUSTOMER_IDENTITY_UPGRADE.md
Normal file
109
docs/hardening/CUSTOMER_IDENTITY_UPGRADE.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Customer identity and live-access upgrade
|
||||
|
||||
These are operator instructions for a reviewed staging rollout, not commands
|
||||
executed by this change. Back up data and verify the exact schema/engine before
|
||||
any production upgrade. Existing customer and conversation records are retained.
|
||||
|
||||
## Additive schema and issuer boundaries
|
||||
|
||||
`CustomerIdentity` adds:
|
||||
|
||||
- `identity_scope`: indexed varchar(80), default empty for legacy rows.
|
||||
- `scoped_key`: nullable varchar(64) with a unique index. Legacy rows remain NULL.
|
||||
|
||||
The normal GORM startup migration adds these definitions. No destructive index
|
||||
replacement, automatic legacy merge, or data deletion is supplied. Test this
|
||||
additive change on a backup with the deployment's actual SQLite/MySQL version.
|
||||
|
||||
A public identity scope is `v1:<jwt|hmac|guest>:SHA256(trimmed channel code)`.
|
||||
The server derives it; clients cannot select an issuer or inject this scope.
|
||||
The unique claim key is SHA-256 of the JSON array containing scope, external
|
||||
source and trimmed external ID. Use the implementation's
|
||||
`ScopedCustomerIdentityKey` helper rather than a hand-built delimiter format.
|
||||
New public registrations insert the claim in the same customer transaction.
|
||||
A competing claim rolls back rather than creating two authorized customers.
|
||||
|
||||
WebSocket routing uses canonical customer ID and channel record ID. Incoming
|
||||
subscriptions and outgoing recipients are checked against current ownership.
|
||||
Raw external IDs are not cross-channel or cross-trust routing keys.
|
||||
|
||||
## Existing sessions and history
|
||||
|
||||
Old customer-session JWTs without the new scope are rejected. The updated client
|
||||
must exchange a fresh token. Until legacy mappings are explicitly reconciled,
|
||||
a newly authenticated scoped identity can receive a separate customer record,
|
||||
not the old account's history. The original records remain available to
|
||||
appropriately authorized operators.
|
||||
|
||||
Before enabling traffic:
|
||||
|
||||
1. Back up data; stop exchanges/writes for the affected scope during reconciliation.
|
||||
2. Run the read-only `scripts/audit-customer-identity-scopes.sql` reports against an
|
||||
approved staging copy after the additive columns exist. Keep report output
|
||||
private: it contains customer identifiers.
|
||||
3. Establish channel and issuer ownership using trustworthy host-side evidence.
|
||||
Legacy `user` rows alone cannot distinguish JWT from HMAC provenance.
|
||||
4. For a proven unique mapping, review a transaction setting both its scope and
|
||||
computed claim key while retaining its customer ID and conversation history.
|
||||
Check all associated conversation channels before making a change.
|
||||
5. Stop on duplicate, inactive, multi-channel or mixed-trust mappings. Never take
|
||||
the first row or merge customers solely because external IDs match. If a new
|
||||
scoped record already exists, reconcile both histories explicitly.
|
||||
6. Reissue customer tokens and verify authorized history, isolation, and rollback
|
||||
behavior on the staging copy before resuming traffic.
|
||||
|
||||
A channel reassigned to a different identity issuer needs a new channel code.
|
||||
Ordinary signing-key rotation intentionally retains the same issuer scope.
|
||||
Use a distinct host JWT signing secret for each independently trusted channel.
|
||||
The existing host JWT contract validates the channel's secret and expiry, but
|
||||
does not require a channel audience claim. Reusing a signing key across unrelated
|
||||
channels lets the holder assert an identity to either channel; scoped storage
|
||||
is not a defense against a shared issuer key. HMAC proofs bind the channel code
|
||||
inside the signed payload. No mandatory JWT audience rollout is introduced here.
|
||||
Legacy duplicate repair remains an operator-reviewed migration, not an automatic
|
||||
startup side effect. The nullable unique claim allows dirty legacy rows to
|
||||
remain isolated without blocking new correctly scoped registrations.
|
||||
|
||||
## Anonymous and authenticated-only channels
|
||||
|
||||
Unsigned exchange issues a new random server-owned anonymous identity. Supplying
|
||||
an old guest external ID is not proof of ownership. Normal resume and refresh
|
||||
use the signed customer-session token. Near-expiry, still-valid credentials are
|
||||
renewed through the rate-limited `POST /api/customer/session_refresh` endpoint,
|
||||
which authenticates the existing session and never creates an anonymous identity.
|
||||
A refresh failure is not silently replaced with a fresh guest. Losing or expiring
|
||||
that credential does not authorize reclaiming history by an external ID.
|
||||
|
||||
An optional boolean `requireSignedIdentity` in a web channel's `configJson`
|
||||
rejects unsigned exchanges and existing guest sessions server-side. Default
|
||||
false retains anonymous channels. The SDK's `requireUserToken` is not a substitute
|
||||
for this policy.
|
||||
|
||||
Use the existing authenticated channel GET and
|
||||
`POST /api/dashboard/channel/update` flow with `channel.update` permission,
|
||||
preserving all unrelated configuration and required fields. No dedicated new UI
|
||||
toggle is provided. Older editors which omit this property preserve a configured
|
||||
true value; an explicit JSON false is needed to disable it. Null and nonboolean
|
||||
values are rejected. This source change does not configure a live channel.
|
||||
|
||||
## Staff and customer live-session revocation
|
||||
|
||||
Staff sockets bind their original login-session ID and absolute expiry, without
|
||||
serializing that internal metadata into API responses. Fresh session, account,
|
||||
role and permission state is checked for incoming operations, event fanout,
|
||||
actual queued writes and idle checks. Scope changes terminate the connection;
|
||||
reconnection must obtain fresh authority. Idle checks run every five seconds;
|
||||
data operations revalidate immediately rather than using a five-second cache.
|
||||
|
||||
Notification sockets cannot subscribe to conversation topics. Dashboard
|
||||
conversation sockets require conversation-view permission. Customer connections
|
||||
recheck token expiry, channel policy, customer state and exact identity mapping.
|
||||
|
||||
This implementation deliberately makes database reads at authorization
|
||||
boundaries. Measure the cost under realistic fanout; any later caching must have
|
||||
complete revocation invalidation rather than silently reintroducing stale access.
|
||||
|
||||
Channel configuration updates and token-secret resets compare the previously read
|
||||
configuration bytes and update timestamp before writing. A stale writer receives
|
||||
a conflict instead of silently overwriting a newly enabled signed-identity policy.
|
||||
Reload the channel and reapply the intended change after reviewing that conflict.
|
||||
71
docs/hardening/README.md
Normal file
71
docs/hardening/README.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Security and reliability hardening
|
||||
|
||||
This independent fork starts from the current source tree of
|
||||
[MonoVerseNS/agent-desk-ng](https://github.com/MonoVerseNS/agent-desk-ng), revision
|
||||
`b06954b0a495389cbb5194e0d388342eabe60636`. It does not import the old Git history.
|
||||
The upstream Apache-2.0 license and notices remain applicable. This document
|
||||
records changes made by this fork; it is not a claim of production certification.
|
||||
|
||||
## Integration baseline
|
||||
|
||||
The isolated integration reconciles public Gitea main `299ea50f` with the reviewed
|
||||
hardening branch `63e04578`. It preserves main's bounded connection attempts,
|
||||
abortable ongoing HTTP recovery and System setup navigation alongside the
|
||||
identity/live-access/draft/setup safeguards. Only the clean public repository
|
||||
history is used. See `VALIDATION.md` for executed checks and unresolved gates.
|
||||
|
||||
## Release gate
|
||||
|
||||
The hardening changes must be reviewed and validated before deployment. Source
|
||||
inspection, a compiler check, a passing isolated test, and live acceptance are
|
||||
separate kinds of evidence. See `VALIDATION.md` for the actual checks performed.
|
||||
No production service, database, credentials, or external deployment is changed
|
||||
by committing this source code.
|
||||
|
||||
## Changes being integrated
|
||||
|
||||
- Separate guest, signed-user, issuer and channel identities. Use canonical
|
||||
customer/channel WebSocket topics and recheck recipients instead of treating
|
||||
a topic subscription as authorization.
|
||||
- Apply conversation-view permission to staff WebSockets; keep notifications
|
||||
separate. Revalidate session expiry, revocation, account state and permissions
|
||||
on long-lived connections. Disabled roles must no longer grant permissions.
|
||||
- Keep drafts and asynchronous responses bound to their identity and dialogue.
|
||||
Preserve drafts on failed sends, protect newly typed revisions, and prevent
|
||||
duplicate concurrent sends. Profile refresh and delayed API errors must not
|
||||
restore or expire the wrong signed-in account.
|
||||
- Turn setup repair into an explicit preview/apply operation. Never resurrect
|
||||
deleted configuration or silently introduce continuous working hours. Apply
|
||||
reviewed writes transactionally and dispatch only after commit.
|
||||
- Make empty setup, initial retry and standalone chat dismissal truthful and
|
||||
recoverable; remove bearer-token-bearing WebSocket URLs from browser logs.
|
||||
|
||||
## Upgrade precautions
|
||||
|
||||
Customer identity scoping is an additive schema and token-contract change, not
|
||||
only a frontend patch. Existing unscoped customer tokens will be rejected.
|
||||
Legacy identities and conversations must be inventoried and reconciled with
|
||||
trusted issuer/channel provenance before expecting their old history in new
|
||||
customer sessions. No automatic customer merge or destructive cleanup is
|
||||
appropriate. See `CUSTOMER_IDENTITY_UPGRADE.md` before any deployment.
|
||||
|
||||
New anonymous sessions are server-issued. Knowing an old anonymous external ID
|
||||
is not sufficient to reclaim its history. Preserve the signed customer-session
|
||||
credential for normal resume; loss or expiry can require a new anonymous session.
|
||||
|
||||
The setup quick-fix API now requires preview and explicit apply. Older callers
|
||||
must not treat a successful preview response as proof that configuration changed.
|
||||
See [the setup contract](SETUP_PREVIEW.md) before integrating custom callers.
|
||||
|
||||
## Verification priorities
|
||||
|
||||
1. Guest/user/channel collisions, unauthorized staff topics, revoked/expired
|
||||
sockets, disabled roles and concurrent identity registration.
|
||||
2. Wrong-recipient drafts, failed and duplicate sends, delayed responses after
|
||||
switching conversations or accounts, and logout during profile refresh.
|
||||
3. Preview/apply tampering, permission denial, rollback, deleted configuration,
|
||||
off-hours schedules, empty-channel simulation and dispatch failure reporting.
|
||||
4. Desktop and narrow-screen interaction checks in Russian, English and Chinese.
|
||||
|
||||
Use temporary, isolated data for checks. Do not run migration/repair commands
|
||||
against a live database merely to validate the code.
|
||||
62
docs/hardening/SETUP_PREVIEW.md
Normal file
62
docs/hardening/SETUP_PREVIEW.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Reviewed setup repairs
|
||||
|
||||
The existing authenticated `POST /api/dashboard/setup/quick_fix` endpoint now uses a two-phase contract. It does not write configuration unless the caller explicitly applies a current preview.
|
||||
|
||||
## Contract
|
||||
|
||||
1. Request a preview with `action` and, for an existing agent/channel/team, its `entity_id`. Omit `apply` or set it to `false`.
|
||||
2. Review `changes`. Each entry names the entity, operation, and exact operational fields before and after. New linked entities are identified by their proposed names until IDs are assigned. Audit timestamps and acting-user metadata are maintained automatically.
|
||||
3. Submit the same action and target with `apply: true` and the returned `preview_token`.
|
||||
|
||||
The response includes:
|
||||
|
||||
- `applied`: whether an explicit apply request completed. An empty `applied_actions` array means no configuration writes were needed.
|
||||
- `changes`: the reviewed plan; an empty array is a valid no-op, not a claim that setup is complete.
|
||||
- `preview_token`: a fingerprint of the acting user, action, target, proposed changes, and current setup configuration.
|
||||
- `applied_actions`: committed operation/entity identifiers, empty on preview.
|
||||
- `warning_codes`: manual follow-up requirements or post-commit failures.
|
||||
- `readiness`: the refreshed diagnostic, omitted if refreshing failed.
|
||||
|
||||
Old callers that omit `apply` receive a read-only preview. Callers must not interpret any HTTP success as a successful mutation. There is deliberately no compatibility path that silently applies an unreviewed repair.
|
||||
|
||||
## Authorization and freshness
|
||||
|
||||
The handler authenticates every request. Setup checks the principal's resolved permission list, matching the normal authorization service. An `admin` or `super_admin` role name alone does not bypass missing permissions. Every planned create or field update also checks its specific permission.
|
||||
|
||||
Apply rebuilds the plan inside one transaction and verifies the fingerprint before writing. A stale preview is rejected and must be reviewed again. Transactional reads use separate GORM statements, preserving row locks without leaking a prior query's table or filters. Updates also compare the target's previous update timestamp. The fingerprint is a freshness/intent check, not an authentication credential or signed authorization grant.
|
||||
|
||||
## Deliberately narrow behavior
|
||||
|
||||
- Broad setup only creates missing defaults. It never rewrites existing configuration, moves existing operators to another team, expands an agent's team pool, or rebinds existing channel routes.
|
||||
- Deleted records are not update candidates and are never restored. Disabled records and zero-rollout settings are preserved. A targeted “enable my profile” action can enable the authenticated user's non-deleted profile only when the preview explicitly shows the change and the required permissions are present.
|
||||
- A targeted handoff change affects only the named eligible agent. Missing teams, route choices, disabled configurations, and rollout changes must be chosen in their existing editors.
|
||||
- Quick fixes never create or modify shifts. Missing schedules and off-hours schedules are distinct diagnostics. Working hours must be entered explicitly in the existing schedule editor.
|
||||
- A newly created default agent is unpublished. Setup does not publish agents. A default channel can be created only after an eligible agent has a published revision, matching the normal channel editor's publication guard. Readiness reports unpublished agents as incomplete.
|
||||
|
||||
Therefore a broad repair is not a promise of a production-ready routing path. Publishing, deliberate routing choices, and explicit schedules may still be required.
|
||||
|
||||
## Transactions and dispatch
|
||||
|
||||
All configuration writes in an apply operation share one transaction. A failed write rolls back the entire repair. Pending dispatch begins only after the configuration transaction commits.
|
||||
|
||||
A dispatch failure produces `dispatch_failed`; it does not undo committed changes or return an error implying that the configuration write should be retried. A diagnostic refresh failure similarly produces `readiness_refresh_failed`. The UI displays these warnings and requires a fresh preview for a subsequent repair.
|
||||
|
||||
The pending-queue query now reports database read errors and applies its existing batch limit in SQL. Its existing newest-first priority and periodic retry semantics are unchanged. This repair does not redesign queue fairness.
|
||||
|
||||
## Simulation
|
||||
|
||||
The simulator is read-only and sends no customer message. `candidate_operators` is always an array, including missing-channel responses; the client also normalizes legacy null values.
|
||||
|
||||
`bypass_ai: true` is explicitly rejected because the service does not implement AI bypass. AI-only paths remain blocked. AI-first/unknown service modes and partial channel rollout are reported as unverified, never as successful operator delivery. Only an eligible, published, full-channel-rollout, human-only route can produce a positive current-eligibility result. That result is a point-in-time diagnostic, not a delivery guarantee or an end-to-end AI execution test.
|
||||
|
||||
## Validation status
|
||||
|
||||
Focused regression tests cover preview-only behavior, explicit apply, stale previews, atomic rollback, deleted/disabled/zero-rollout preservation, named-target scope, off-hours versus missing schedules, role-name permission bypass, status-only permissions, empty arrays, simulation limitations, publication requirements, and post-commit failures.
|
||||
|
||||
The focused setup regressions and their race-enabled run passed in the isolated
|
||||
2026-10-16 integration baseline. Go formatting/compilation, declared-dependency
|
||||
frontend typecheck and Node regression tests were also executed. The remaining
|
||||
combined-tree validation, frontend release build, actual MySQL upgrade and browser
|
||||
acceptance gates are recorded in [VALIDATION.md](VALIDATION.md). No production
|
||||
configuration or data was changed. Passing isolated tests does not establish
|
||||
production readiness or authorize an upgrade.
|
||||
185
docs/hardening/VALIDATION.md
Normal file
185
docs/hardening/VALIDATION.md
Normal file
@@ -0,0 +1,185 @@
|
||||
# Validation status: isolated checks, not a production release
|
||||
|
||||
Validated on 2026-10-16 against the integrated public-source baseline: Gitea main
|
||||
`299ea50fb5bb6874c3fca96277fce187205ac20a` and hardening-review
|
||||
`63e045788623e88f7fd7b44102e649e08f4c1f1c`, merged as `3868181`, with the follow-up
|
||||
corrections listed below. This baseline does not include later knowledge-base
|
||||
localization or operator-availability feature work. Its historical results are
|
||||
retained below; the final combined-tree checks are recorded separately at the end
|
||||
of this document.
|
||||
|
||||
## Isolation and toolchain
|
||||
|
||||
- Go 1.26.9, Node 24.19.0, pnpm 10.30.2, locked TypeScript 6.0.3/Next 16.2.11.
|
||||
- Official Go archive checksum and downloaded Go module checksums verified.
|
||||
Frontend dependencies installed with the frozen lockfile and lifecycle scripts
|
||||
disabled. Dependency manifests/lockfiles were not changed.
|
||||
- Tests ran with a cleared application environment, dedicated temporary/cache
|
||||
directories, temporary or in-memory SQLite fixtures, mocked frontend APIs and
|
||||
loopback WebSocket fixtures. No production configuration, credentials,
|
||||
databases, application server, or external support messages were used.
|
||||
- The service test harness rejects non-loopback HTTP requests before the
|
||||
underlying transport/DNS, including redirects. Concrete Slack, Telegram,
|
||||
Discord, Zalo and HTTP email clients were inspected to confirm their default
|
||||
transport usage. The only test WebSocket dialer targets its own loopback server.
|
||||
- Service fixtures explicitly replace the transitive AI-runtime hook with a
|
||||
test-only no-op. Dedicated hook tests still install their own spy; AI-runtime
|
||||
behavior is not claimed as covered by the service suite.
|
||||
- `TestHandleRequest*` email/SMTP cases were excluded from the expanded service
|
||||
run: raw SMTP does not use the HTTP guard. No unrestricted `go test ./...` was
|
||||
run. This exclusion is an isolation limit, not a passing result.
|
||||
|
||||
## Historical baseline completed checks
|
||||
|
||||
- `gofmt` on all changed Go files and `git diff --check`.
|
||||
- `go test ./internal/pkg/openidentity`: passed, including scoped issuer/channel,
|
||||
signed-policy and signature-boundary tests.
|
||||
- `GOMAXPROCS=2 go test -p 2 -race -tags dev ./internal/pkg/eventbus ./internal/pkg/openidentity`:
|
||||
passed.
|
||||
- Focused service security/setup/realtime suite with `-race -tags dev`: 46
|
||||
top-level tests, 102 successful test/subtest events, no failures or skips.
|
||||
Covers identity collisions/concurrent claims, live session revocation,
|
||||
permission boundaries, actual WebSocket pumps, channel CAS, preview/apply,
|
||||
rollback and dispatch outcomes, plus the transport guard and redirect check.
|
||||
- `GOMAXPROCS=2 go test -p 2 -tags dev ./internal/services -skip '^TestHandleRequest' -count=1`:
|
||||
passed, 217 top-level tests and 295 successful test/subtest events.
|
||||
- `GOMAXPROCS=2 go build -p 2 -tags dev -buildvcs=false ./...`: passed. The `dev`
|
||||
embedding mode is the repository CI test mode. VCS stamping was unavailable in
|
||||
the isolated worktree and was disabled only for this compile check.
|
||||
- `cd web && pnpm typecheck`: passed with declared locked dependencies before
|
||||
the later six-file lint cleanup. The post-cleanup retry was killed with exit
|
||||
137 without a TypeScript diagnostic; the final combined tree must rerun it.
|
||||
- All frontend Node test files under app/components/hooks/i18n/lib:
|
||||
226 tests passed, no failures or skips. This includes the SDK, draft revisions,
|
||||
account/customer-session isolation, setup contract, abortable realtime recovery,
|
||||
hidden/unmounted lifecycle and navigation checks.
|
||||
- Four additional offline component-lifecycle regressions passed after the
|
||||
six-file lint cleanup (bulk-move selection/stale responses, palette snapshots,
|
||||
notification cleanup, editor/origin initialization). The earlier 226-test full
|
||||
run predates these four additions.
|
||||
- Full `pnpm lint`: exit 0 after localized lifecycle corrections, with zero
|
||||
errors and 45 existing warnings. No lint rules were disabled.
|
||||
|
||||
## Corrections found by execution
|
||||
|
||||
- The old realtime test fixture lacked required conversation counters. It now
|
||||
matches the DTO; the history/realtime merge assertion was retained unchanged.
|
||||
- Async assignment notifications could outlive their test database. The eventbus
|
||||
now exposes a drain for already-published handlers, and the fixture waits before
|
||||
restoring global state. Nested publishes/error callbacks have a regression
|
||||
test. No sleep or disabled race check is used.
|
||||
- Setup background responses are fenced by effect lifetime/request generation.
|
||||
The staff realtime hook captures its user ID in the effect rather than writing
|
||||
a ref during render. VM test harness variable names satisfy Next ESLint rules.
|
||||
- Four missing generic setup locale keys were supplied in English, Russian and
|
||||
Chinese. Existing locale-integrity assertions are unchanged.
|
||||
- Three stale source-test harnesses were aligned to current route groups and
|
||||
component ownership while retaining realtime placement, accessible label,
|
||||
trigger-style and collapsed-navigation checks.
|
||||
- Seven existing lint errors across six components were corrected without rule
|
||||
suppressions: browser-origin/palette hydration now uses external snapshots;
|
||||
bulk-move targets reset on dialog/base changes and obsolete directory results
|
||||
are ignored; initial notification requests cannot update after cleanup;
|
||||
fullscreen initialization and toolbar construction no longer need the flagged
|
||||
mount-state effect or mutable ref-bearing action list.
|
||||
|
||||
## Historical baseline remaining release gates
|
||||
|
||||
- Full-project lint has 45 existing warnings. The final feature integration must
|
||||
rerun lint, typecheck and the complete Node suite after all source changes.
|
||||
- The canonical frontend export (`pnpm build`) did not complete. An initial
|
||||
cross-worktree node_modules symlink was replaced by a local offline frozen-lock
|
||||
installation. The subsequent default Turbopack build reached optimized
|
||||
compilation, then was killed with exit 137 and no source diagnostic while the
|
||||
shared environment had little free memory. This is not a successful build; a
|
||||
low-concurrency build on the final combined tree remains required. No build,
|
||||
typecheck or security checks were disabled to bypass this result.
|
||||
- Actual MySQL CAS/nullable-unique-index behavior and additive schema upgrade/
|
||||
identity reconciliation on an approved staging backup have not been run.
|
||||
- Browser acceptance at desktop/narrow widths in Russian, English and Chinese
|
||||
has not been performed for this integrated baseline.
|
||||
- Production migration, service startup and deployment have not been performed.
|
||||
|
||||
SQLite results do not establish MySQL behavior. Automated tests do not establish
|
||||
visual acceptance or a safe production upgrade. Preserve the identity/token
|
||||
upgrade precautions in `CUSTOMER_IDENTITY_UPGRADE.md` and preview/apply contract
|
||||
in `SETUP_PREVIEW.md` before any rollout.
|
||||
|
||||
## Final combined source validation (2026-10-16)
|
||||
|
||||
The combined canonical checkout at `1ec72508cdda2dfe3b3eed3158f8ba930d76d260`
|
||||
includes security/realtime integration, knowledge localization, operator
|
||||
availability/calendar/presence, final locale changes and the two fixture-only
|
||||
corrections described below. These results are distinct from the historical
|
||||
baseline above and from the separately integrated CoverIt vendor tree.
|
||||
|
||||
The same sanitized toolchain, temporary SQLite fixtures and loopback-only test
|
||||
transport were used. No application server, production database, credentials,
|
||||
migration or external support message was used. Raw SMTP `TestHandleRequest*`
|
||||
cases remain excluded, and the service-suite AI hook remains a test-only no-op;
|
||||
neither scope is claimed as covered. No unrestricted `go test ./...` was run.
|
||||
|
||||
### Completed combined checks
|
||||
|
||||
- All changed Go files are `gofmt`-clean; `git diff --check` passed.
|
||||
- `GOMAXPROCS=2 go test -p 2 -tags dev ./internal/services -skip '^TestHandleRequest' -count=1 -timeout=5m -json`:
|
||||
passed, 240 top-level tests / 333 test-and-subtest events, no failures or skips.
|
||||
The same audited scope with `-race` passed with identical counts and no race
|
||||
report (181.272 seconds).
|
||||
- Full race suites for `internal/pkg/eventbus`, `internal/pkg/openidentity` and
|
||||
`internal/pkg/i18nx`: 48 top-level tests / 78 successful events.
|
||||
- Focused dashboard availability permission/ACL race tests: 4 tests passed.
|
||||
- Focused knowledge handler, builder and RAG localization race tests: 12
|
||||
top-level tests / 18 successful events, with mocked provider callbacks and no
|
||||
external application calls.
|
||||
- `GOMAXPROCS=2 go build -p 2 -tags dev -buildvcs=false ./...`: passed. This is a
|
||||
complete source compile in the repository's test embedding mode, not a binary
|
||||
containing the production web export. VCS stamping was disabled only for the
|
||||
local compile environment.
|
||||
- `NODE_OPTIONS=--max-old-space-size=1024 pnpm typecheck`: passed.
|
||||
- Every frontend `*.test.mjs` under app/components/hooks/i18n/lib, run with
|
||||
`node --test --test-concurrency=1`: 262 tests in 14 suites passed; zero failures,
|
||||
cancellations or skips.
|
||||
- Full `pnpm lint`: exit 0, zero errors and 42 warnings. No rules were disabled.
|
||||
|
||||
The first combined service run identified two obsolete fixture assumptions,
|
||||
not production-code failures. The realtime operator fixture now reuses the
|
||||
existing `conversation.view` permission without changing its state. Only the
|
||||
readiness success scenario that expects an eligible human now seeds an employee,
|
||||
current permission, login session and bounded live-presence lease. Negative
|
||||
permission scenarios and all original behavior assertions were retained. The
|
||||
full normal and race results above were obtained after these fixture repairs.
|
||||
|
||||
### Asset assembly and packaging limit
|
||||
|
||||
- Both frontend projects used unchanged frozen lockfiles with dependency
|
||||
lifecycle scripts disabled. Web dependencies totalled 978 locked packages;
|
||||
Flowgram dependencies totalled 953. Official Go/module integrity checks passed.
|
||||
- Flowgram's production `pnpm build` passed and generated its complete public
|
||||
assets. Third-party parser warnings were reported, without build errors.
|
||||
- The SDK's official `pnpm build:sdk` ran twice; both outputs were deterministic
|
||||
and byte-identical to the tracked bundle. No generated SDK change is required.
|
||||
- The default Next 16.2.11 Turbopack production build did not complete: optimized
|
||||
compilation ended with exit 137 (`Killed`), without a source diagnostic.
|
||||
- A sequential supported `next build --webpack` attempt, with a 1 GiB Node heap
|
||||
and bounded workers, ended with a build-worker `SIGKILL`, also without a source
|
||||
diagnostic. This was not a successful alternate export.
|
||||
- One distinct experiment used the [documented memory optimization](https://nextjs.org/docs/app/guides/memory-usage)
|
||||
`experimental.webpackMemoryOptimizations` option and a 640 MiB Node heap. It
|
||||
ended with an explicit JavaScript heap-out-of-memory error and worker
|
||||
`SIGABRT`. The temporary config was restored byte-for-byte afterward. No build
|
||||
check, typecheck, security setting, font or application asset was bypassed or
|
||||
mocked. Official Google font CSS and a real font asset were reachable through
|
||||
the existing trusted proxy configuration.
|
||||
- Dependency manifest and lockfile hashes, and the tracked SDK hash, remained
|
||||
unchanged by assembly; no production source/config edit was retained. No native production-embedded binary was
|
||||
assembled because a complete Next export was not available. Flowgram/SDK
|
||||
success and the `-tags dev` Go compile do not satisfy that packaging gate.
|
||||
|
||||
The default frontend export and complete native assembly therefore still need
|
||||
a suitably provisioned isolated build environment. These failures do not prove
|
||||
a source defect, and the passed tests do not establish a deployable release.
|
||||
Actual MySQL behavior, approved staging upgrade/identity reconciliation, real
|
||||
browser desktop/narrow-width acceptance in Russian/English/Chinese and production
|
||||
migration/startup/deployment remain unverified. Preserve all upgrade precautions
|
||||
above. No cross-platform or LanceDB release assembly was attempted.
|
||||
55
docs/knowledge-localization.md
Normal file
55
docs/knowledge-localization.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Knowledge retrieval localization and derived data
|
||||
|
||||
## Locale and request lifetime
|
||||
|
||||
The dashboard debug-search, debug-answer, and index-build handlers pass their HTTP request context into RAG. `i18nx.WithLocale` adds the locale already selected by `i18nx.Locale` without dropping cancellation, deadlines, or parent values. `i18nx.LocaleFromContext` falls back to the configured `DefaultLocale` for existing callers and background jobs.
|
||||
|
||||
This does **not** change application-wide locale detection. In particular, `ResolveRequestLocale` continues to use the configured default rather than trusting request language headers. Callers with an explicitly selected locale can carry it through the context helper.
|
||||
|
||||
Built-in debug-answer system/user prompts, no-answer and model-error fallbacks, answer-status names, and context wrappers use the English, Russian, and Chinese catalogs. Configured custom prompts, model output, user questions, document bodies, and FAQ source answers are not translated or rewritten. Cancellation is not converted into a completed fallback answer.
|
||||
|
||||
## FAQ labels and existing index data
|
||||
|
||||
FAQ question, similar-question, and answer labels are application-owned. The source strings are separate inputs and are preserved without trimming or substring replacement. New FAQ index content uses the configured default locale, not whichever request locale an administrator supplies. This keeps index generation consistent for a deployment.
|
||||
|
||||
For live retrieval only, the stored FAQ chunk is compared byte-for-byte with a complete serialization of the current authoritative FAQ record:
|
||||
|
||||
- The exact frozen pre-localization Chinese format is recognized.
|
||||
- The generated format in each currently supported locale is recognized.
|
||||
- Only a complete match permits rendering the application-owned labels in the selected context locale. This also safely handles newlines or literal `问题:`, `相似问:`, and `回答:` strings inside source content, because they are never parsed as separators.
|
||||
- If the source has since changed, the chunk was edited manually, the stored format is unknown, or no exact match exists, the stored text remains unchanged. Document chunks never enter this FAQ conversion path.
|
||||
|
||||
Locale changes and reads do not update stored chunks, vectors, hashes, FAQ records, or historical retrieval logs. Previous answers and hit snippets are historical evidence and remain as recorded. Newly generated logs record the localized content actually used for that new request; they do not relabel old answers after the fact.
|
||||
|
||||
Unverifiable legacy or stale chunks may therefore still show old generated wording. Rebuilding the affected index from its current source through the existing authorized indexing action is the deliberate way to regenerate that derived data. A rebuild can change embeddings, scores, and retrieval behavior and should be validated against representative queries before a production rollout. This change contains no migration, automatic rebuild, or production data write.
|
||||
|
||||
## Failure responses
|
||||
|
||||
Immediate debug/index failures use stable localized summaries instead of exposing SQL, provider, or network diagnostics. Known catalog-backed application errors without arbitrary diagnostic arguments retain their message and code even when wrapped by a provider error. Unrecognized errors, raw code errors, and errors containing interpolation arguments are replaced with a generic localized operation error. Stored operational details are not returned by this boundary.
|
||||
|
||||
## Focused validation
|
||||
|
||||
Run the isolated tests without external model or vector service credentials:
|
||||
|
||||
```sh
|
||||
go test -tags dev ./internal/pkg/i18nx ./internal/ai/rag ./internal/handlers/dashboard
|
||||
```
|
||||
|
||||
Coverage includes locale fallback and isolation; request cancellation/deadlines; localized no-hit, model-error, empty-model and successful generation branches; byte-for-byte source preservation; exact legacy matching and stale-content preservation; localized context wrappers; and safe error responses in all three locales. The model-generation tests inject a local function and the cancellation tests return before database or provider access.
|
||||
|
||||
## API labels, index failures, and FAQ spreadsheets
|
||||
|
||||
Knowledge-base, document, FAQ, directory, and retrieval-history DTO builders accept an optional locale; omitted locales use the configured default for compatibility. Dashboard handlers supply their selected locale explicitly. Stable type/status/index codes remain unchanged. The UI maps those codes with shared catalog helpers rather than trusting legacy display-name strings. Unknown values use a localized unknown label.
|
||||
|
||||
Document and FAQ DTOs return a safe localized indexing-failure summary, not the stored provider or SQL diagnostic. The UI applies the same safe summary when connected to an older server. This change does not rewrite stored operational diagnostics or add a public raw-error endpoint.
|
||||
|
||||
FAQ spreadsheet templates and export headers use the selected locale. Import accepts Russian headings and both legacy Chinese and English aliases. Export preserves stored question, answer, similar-question, and note values; import preserves authored question, answer, note, and individual nonempty similar-question text, including surrounding whitespace. Newlines remain the spreadsheet format's delimiter between similar questions. Missing optional columns no longer copy the question column accidentally. No sample-data initializer or table-reset command is needed or run.
|
||||
|
||||
Additional isolated checks:
|
||||
|
||||
```sh
|
||||
go test -tags dev ./internal/builders ./internal/services -run 'TestKnowledgeBuilders|TestKnowledgeFAQExcel|TestImportKnowledgeFAQs|TestDeleteKnowledgeBase|TestBuildKnowledgeBaseModel' -count=1
|
||||
node --test web/lib/knowledge-i18n.test.mjs
|
||||
```
|
||||
|
||||
These are logic and contract checks, not production or browser acceptance. Before rollout, validate the real KB list, content dialog, spreadsheet import/export, debug search/answer, and history in Russian and English at desktop and narrow widths. Use isolated fixtures and do not run the destructive sample-data initializer.
|
||||
65
docs/support-availability-ui.md
Normal file
65
docs/support-availability-ui.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Support availability UI
|
||||
|
||||
Modified in this independent fork; see `hardening/README.md`. Source-only feature,
|
||||
not a production deployment or a claim of browser acceptance.
|
||||
|
||||
## Interfaces
|
||||
|
||||
- System → Support availability: deployment-default IANA timezone and explicit
|
||||
team calendar mode. A team can keep dated shifts or opt into recurring weekly
|
||||
periods and date exceptions. Switching modes never deletes dated shifts.
|
||||
- Recurring periods support split shifts, overnight ends and `00:00`–`24:00`.
|
||||
A closed date sends no open periods; unsaved hidden period inputs are retained
|
||||
while toggling the date back to open. Draft preview calls the server evaluator.
|
||||
- Existing dated-shift screens explain that stored rows are inactive for teams
|
||||
using recurring mode. They do not imply both schedules are combined.
|
||||
- Header/workbench availability controls persist Ready, Busy, Break, Unavailable
|
||||
and a half-open vacation interval. The backend captures the return status when
|
||||
vacation is first scheduled. Logging in does not reset an active absence.
|
||||
- Vacation and preview inputs explicitly use the browser timezone, show exact
|
||||
UTC instants, reject nonexistent DST times, and expose a choice for repeated
|
||||
local times. Recurring calendar results use the configured timezone. Opaque
|
||||
legacy process timezone names render next-opening instants explicitly in UTC.
|
||||
- Admin profile rows show live presence, effective working status and server
|
||||
eligibility separately. Dedicated status changes are independent of general
|
||||
profile edits. The unused offline-message flag is retained but read-only and
|
||||
labeled as legacy, never as permission to assign to disconnected staff.
|
||||
- Transfer candidates come from the server's conversation-scoped manual
|
||||
eligibility endpoint, not a client-side `serviceStatus === Idle` guess.
|
||||
- Customer chat shows technical connection separately from aggregated human
|
||||
support availability. No employee identity, absence interval or private reason
|
||||
is sent by the public status endpoint. Scheduled opening copy does not promise
|
||||
that an operator will be connected.
|
||||
|
||||
## Safety and interaction coverage
|
||||
|
||||
The API adapter uses the existing request/session envelope and bounds background
|
||||
availability reads to 10 seconds. Components discard unmounted/old-session
|
||||
responses. Mutations and post-confirmation callbacks are checked against the
|
||||
captured authenticated session. Saves are single-flight; failed saves retain
|
||||
inputs. Read-only/preview Enter does not submit calendar changes. Closing a dirty
|
||||
form, changing scope, and internal/browser navigation warn before discarding.
|
||||
|
||||
The calendar is a scoped configuration form rather than a paginated resource, so
|
||||
it uses DashboardPage/Toolbar, react-hook-form, existing field/input/combobox and
|
||||
confirmation components. The existing agent CRUD/calendar components remain in
|
||||
place. All new copy is available in Russian, English and Chinese.
|
||||
|
||||
## Checks performed
|
||||
|
||||
- Full TypeScript `--noEmit --incremental false`: passed.
|
||||
- Focused availability helper/API tests: 13 passed.
|
||||
- Additional independent transport/locale edge tests: 10 passed, including
|
||||
independent deadlines, retry recovery, EU DST and Lord Howe 30-minute overlap.
|
||||
- Focused lifecycle tests: 6 passed, including StrictMode effect restart,
|
||||
duplicate saves, late session changes and confirmation after unmount.
|
||||
- Focused lint: no errors; existing agent avatar `<img>` warning remains.
|
||||
- Git whitespace check: passed.
|
||||
|
||||
The lifecycle tests execute actual component handlers in an isolated hook
|
||||
harness; they are not rendered-browser tests. Browser launch was blocked by the
|
||||
execution environment. Desktop/narrow-width rendering, focus behavior, keyboard
|
||||
interactions, native date/time controls, and real Next.js Back/Forward handling
|
||||
still require browser acceptance. No live account, customer message, production
|
||||
database or deployment was used. The integration owner records combined build
|
||||
and broader regression results separately in `hardening/VALIDATION.md`.
|
||||
Reference in New Issue
Block a user