Skip to content

Support Console — runtime, access and recovery contract

Status: prepared source implementation; not applied to an actual database or verified in the pinned Web runtime. Owner Task: NS5-009. Product paths below are relative to new-system/.

Screen and authority

/support mounts SupportWorkspace. Settings has a guarded Support Console navigation action; it uses the existing unsaved-draft decision and selected-tenant rule before navigating. The standard Web base path is applied to all browser API URLs. No menu/role rows are seeded.

The current authenticated actor, effective subject and tenant come from resolveSemanticRequest. SYSTEM uses only its already server-resolved selected tenant, never a query-supplied replacement. A support grant authorizes only the read-only diagnostic path. It neither issues an authentication session nor changes ordinary business, Admin, Engineering or QA permissions. No implicit SUPER_ADMIN approval bypass or new role is created. Existing Admin authorization owners remain unchanged; actual cross-surface role testing remains an acceptance requirement.

Grant management additionally requires the configured current capability and the existing sessionHasCapabilities action/entitlement decision. Unresolved roles cannot approve grants. A viewer receives only actor/subject-matching grants in the resolved tenant. Candidate usernames and approval reasons are available only to an authorized manager. The implementation issues grants to active users of the target tenant; it does not introduce cross-tenant impersonation or staff accounts.

Explicit deployment input

CTC_SUPPORT_POLICY is JSON with the following required fields. There are no seeded/default values:

Field Contract
approverCapability A Product-owner-approved existing member of BUSINESS_CAPABILITY_IDS; current persisted assignment/action must allow it
maxGrantMinutes Positive safe integer; limits each grant's start-to-expiry duration
auditRetentionDays Positive safe integer; records a minimum retained-until date, not automatic deletion

The validated webRuntimeConfig().environment scopes persistent rows. The request cannot choose a remote environment. Missing/invalid policy, missing schema or audit failures return unavailable; there is no in-memory, logging-only or grant-all fallback. These configuration values have not been selected or changed in the target environment in this session.

Public owners and execution

  • @ctc/identity-access/support: existing audited read core plus grant decoding, explicit policy, issue/revoke orchestration and storage port. It owns identity/scope/time and current capability rules.
  • @ctc/observability/support: existing allowlisted diagnostic projection. No raw message, stack, arbitrary metadata or Error.name is returned. The ordinary logger is not used as an audit receipt.
  • @ctc/db-mysql/support: concrete MySQL persistence/query implementation. SQL and numeric aliases remain here. The app imports these admitted subpaths, not cross-package source paths.
  • apps/web/src/bootstrap/supportRuntime.ts: actual session/pool/policy/query/audit composition. It re-resolves the current session and role state around asynchronous diagnostic execution.
  • apps/web/src/transport/supportManagement.ts and supportConsole.ts: HTTP decoding, audited dispatch, stable errors and no-store responses. Existing trusted mutation-origin logic is reused.

HTTP contract

Endpoint Method / result
/api/support/grants GET lists at most 200 recent scoped grants, current view identity and manager-only candidates/policy ceiling
/api/support/grants?grantUuid=UUID GET authoritative operation readback; hidden/foreign scopes are not returned
/api/support/grants POST issues one normalized UUID grant and its approval audit atomically
/api/support/grants PATCH revokes one grant using a retained operation UUID; repeats must match the existing receipt
/api/support/audit GET manager-only, 100 records/page, optional grantUuid and UUID cursor; no raw document/reason projection
/api/support/diagnostics GET requires tenantUuid, grantUuid and optional recordUuid; delegates to the acknowledged audited core

Grant POST fields: tenantUuid, grantUuid, actorUuid, startsAt, expiresAt, reason and scope. Scope is TENANT or RECORDS with unique canonical UUIDs (1–200). The UI offers one optional operation-event record. Tenant/approver come from the current session, and subject is direct-access null. Timestamps are canonical UTC ISO strings. The UI accepts local expiry and converts it; a duration ceiling is not an invented default grant duration. Reason is trimmed, 1–500 characters.

PATCH fields: tenantUuid, grantUuid, operationUuid and reason. The browser retains the same prepared body and UUID while an outcome is uncertain. A successful response is followed by authoritative readback. Failure/timeout/lost acknowledgement does not trigger an automatic second mutation. Manual retry first reads the stored operation again and requires current management authority. A changed tenant, request body or conflicting revoke is not success. Acknowledged writes followed by failed/denied readback remain pending; input is not silently discarded. Pending/dirty work blocks access refresh and has an unload warning. Browser state is not persisted as a credential.

All API responses are private/no-store with Cookie/Authorization Vary and nosniff. Error responses expose fixed codes only: authentication 401, invalid input 400, denied 403, conflicting receipt 409, unavailable/audit/unknown write 503 and cancellation 499. Unsupported methods never return diagnostic data. Origin checks, method checks and invalid authenticated input have bounded audit entries. A rejected request before authentication/config/storage availability cannot manufacture an audit receipt; existing authentication logging remains a separate owner.

MySQL and append-only evidence

The sole migration source is the existing repository lineage: database/migrations/2026-09-12_ns5-009_support_access.sql (outside the current Product root). It creates only T_SUPPORT_ACCESS_GRANT and T_SUPPORT_ACCESS_AUDIT and two audit DML-blocking triggers. It seeds no accounts, capabilities, grants or policy. Applying it requires an explicitly authorized target. Re-running an already-installed trigger is not a destructive drop/recreate path; the migration owner must inspect prior installation first.

Grant rows use binary UUIDs, environment, actor/subject/tenant indexes, a start/expiry window, byte-stable document text/hash, and revocation operation/hash. Grant+approval audit and revoke+audit share one transaction. After a commit acknowledgement error, fresh pooled readback checks the exact receipt before returning success. A failed rollback destroys the poisoned connection when supported; the normal mysql2 connection supports it. No unknown write is blindly replayed.

Audit rows use a unique event UUID, scoped indexes, document hash and minimum retention timestamp. Writes are awaited and read back, including lost insert acknowledgements. UPDATE/DELETE triggers prevent ordinary DML edits; checksums detect mismatched bytes. These do not protect against an administrator able to drop triggers, truncate/drop tables or rewrite both document and hash. Runtime DB credentials must not have those administrative powers. No purge endpoint, scheduled deletion or legal-hold policy is implemented or implied. Audit reasons remain private security records.

Diagnostic data meaning

The actual source is T_OPERATION_EVENT, which is a notification ledger, not a generic job outbox. The dashboard reports tenant-source DB reachability and unacknowledged notifications in the last 14 days. The queue failure value remains unknown rather than inventing a worker failure count. At most 100 ERROR/CRITICAL notification records from the same window are returned. Only a safe error code, event UUID and observation timestamp are projected. No raw payload/message/PII is exposed.

recordUuid means operation-event UUID, not an arbitrary Booking/Invoice/customer UUID. Its query is tenant-bound and omits aggregate health/checks/counts. It does not promise a universal business-record drill-down or global service-health conclusion.

The shared ListDetailTemplate preserves a previously read dashboard beside current record detail within the same authenticated grant only. Refresh, denial, failure, expiry, disposal or a changed scope clears the relevant displayed/retained data. Stale async responses cannot resurrect data. The expiry timer re-arms for long windows and rechecks when visibility changes. Revocation is enforced on subsequent/in-flight server reads, not represented as a live push notification to idle browsers.

Verification and release boundary

Prepared source tests: 206 PASS on cloud Node22.16.0 / TS5.8.3, synthetic grants/SQL connections and native Request/Response. Six deliberate mutations were detected and restored. Selected .ts typing and TSX syntax passed; these do not typecheck the actual Next/bootstrap/shared React dependencies. The installed browser React16 lacks current Hooks, so browser execution is BLOCKED, not PASS. No actual MySQL migration, SQL trigger execution, DB privilege review, pinned React19/Web build, authenticated role/CRUD, independent review or production test was performed.

Before acceptance, use the repository-pinned environment to execute Task/native tests, apply the reviewed schema to an authorized disposable target, exercise grant/read/detail/revoke/audit with secret fixtures and two tenants, test existing Admin capability denial, prove commit-loss recovery and append-only triggers, then clean the exact fixtures and verify desktop/narrow rendering. DB compatibility is BACKWARD_COMPATIBLE_STAGED: existing endpoints/schema are unchanged, and the new feature requires its explicit configuration and schema before use. It is not deployed by preparing source.

---

Source: new-system/docs/pages/SUPPORT_CONSOLE.md — ctc repository · This page is an English translation of the source document. Identifiers, file paths and code symbols are unchanged.