mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 02:15:36 +00:00
Introduces structured per-phase tracing for OAuth flows so users see branch-specific error messages instead of a generic "token not found" fallback. - New oauth-trace/ module: recorder, redactor, three sinks (in-memory ring buffer, verbose stdout, opt-in JSONL file at ~/.ccs/logs/oauth-YYYYMMDD.log mode 0o600 via CCS_OAUTH_LOG_FILE=1) - Branch-specific diagnostics for: URL-not-displayed, callback-not-observed, binary-error, token-exchange-error, session-expired, token-file-missing; UNKNOWN fallback preserves prior UX - Trace recorder threaded through oauth-process.ts spawn/stdout/stderr/ exit lifecycle; SIGINT/SIGTERM cleanup flushes recorder and records Cancelled - Redactor strips code, state, access_token, refresh_token, id_token, client_secret, code_verifier, device_code, assertion, subject_token, plus Authorization: Bearer headers; covers URL fragments, URL-encoded keys, and arrays - 68 new tests including adversarial regression cases for redactor bypass attempts Phases for paste-callback path and cross-profile failure-matrix deferred to follow-up #1207. Closes #1206
64 lines
2.0 KiB
TypeScript
64 lines
2.0 KiB
TypeScript
/**
|
|
* OAuth trace event taxonomy.
|
|
*
|
|
* Single source of truth for phase IDs that flow through the OAuth pipeline.
|
|
* Additive only — never remove or renumber.
|
|
*/
|
|
|
|
export enum OAuthTracePhase {
|
|
// Pre-flight
|
|
PreflightStart = 'preflight.start',
|
|
PreflightOk = 'preflight.ok',
|
|
PreflightPortBlocked = 'preflight.port_blocked',
|
|
|
|
// Binary process lifecycle (CLIProxyAPI Go binary)
|
|
BinarySpawn = 'binary.spawn',
|
|
BinaryStdout = 'binary.stdout',
|
|
BinaryStderr = 'binary.stderr',
|
|
BinaryExit = 'binary.exit',
|
|
|
|
// Browser / URL flow (Authorization Code)
|
|
AuthUrlDisplayed = 'auth.url_displayed',
|
|
BrowserOpened = 'browser.opened',
|
|
CallbackObservedHeuristic = 'callback.observed_heuristic',
|
|
|
|
// Paste-callback (CCS-owned end-to-end)
|
|
PasteCallbackPrompted = 'paste.prompted',
|
|
PasteCallbackReceived = 'paste.received',
|
|
PasteCallbackInvalid = 'paste.invalid',
|
|
PasteCallbackSubmitted = 'paste.submitted',
|
|
|
|
// Token exchange + persistence
|
|
TokenExchangePending = 'token.exchange_pending',
|
|
TokenFileAppeared = 'token.file_appeared',
|
|
TokenFileMissing = 'token.file_missing',
|
|
|
|
// Provider-specific gates (Phase 4/5 extend)
|
|
ProjectSelectionPrompted = 'project.selection_prompted',
|
|
ProjectSelectionResolved = 'project.selection_resolved',
|
|
AgyResponsibilityPrompted = 'agy.responsibility_prompted',
|
|
AgyResponsibilityResolved = 'agy.responsibility_resolved',
|
|
|
|
// Terminal states
|
|
Timeout = 'timeout',
|
|
Cancelled = 'cancelled',
|
|
Error = 'error',
|
|
}
|
|
|
|
/** A single OAuth trace event. `data` MUST be redacted before construction. */
|
|
export interface OAuthTraceEvent {
|
|
sessionId: string;
|
|
provider: string;
|
|
phase: OAuthTracePhase;
|
|
ts: number; // Date.now()
|
|
elapsedMs: number; // since recorder.start()
|
|
data?: Record<string, unknown>;
|
|
error?: { code?: string; message: string };
|
|
}
|
|
|
|
/** Sink interface — accept events that have already been redacted. */
|
|
export interface OAuthTraceSink {
|
|
write(event: OAuthTraceEvent): void;
|
|
flush?(): Promise<void>;
|
|
}
|