Day 12 of 100·Project Deep-Dive Day·830 words

Zoth end-to-end — the walkthrough you must narrate flawlessly

One access request, from login to GitHub collaborator to automatic expiry. This is THE interview story.

Know your own system

This is the single most important article of the even days. "Walk me through what happens when X" is the question that exposes whether you built something or assembled something. Narrate this until it's reflexive. Every step is real code — I name the files.

Step 0 — Authentication

POST /api/v1/auth/login → JWT HS256, claims {sub: userID, tid: tenantID, email, roles, iat, exp} (1 h expiry, secret ≥ 32 bytes enforced at startup). On every subsequent request, middleware.authenticateJWT parses the token, then — crucially — re-resolves the user's role from the database rather than trusting the claim, so role changes and user deletion take effect immediately. API keys are the machine path (X-API-Key, SHA-256-hashed with 8-char prefix, write-scope enforcement on non-GET). Tenant context comes from tid and is asserted by middleware.Tenant().

Step 1 — Request creation

POST /api/v1/access-requests with {resource_id, justification} → jit.Service.RequestAccess (internal/services/jit/service.go):

  1. Tenant check: resource is fetched via GetResourceByIDAndTenant (INNER JOIN through integrations — resources have no tenant_id of their own; ownership flows through their parent integration).
  2. Policy evaluation: policy.EvaluateForRequest loads the tenant's active policies ordered by priority DESC and returns the first whose match_conditions match the resource + the user's groups. No match → the request proceeds unrouted (any member can decide); matched → policy_id is stamped on the request.
  3. RAG enrichment (if GEMINI_API_KEY set): risk score, approval likelihood, ETA, suggested duration are computed and stored on the request row (rag_risk_score etc.) — advisory fields, never decisions.
  4. Suspicious-activity check: four signals (temporal anomaly, privilege escalation on HIGH/CRITICAL sensitivity, velocity spike for first-time requesters, cross-domain pattern) — if any trigger: audit row + alert.suspicious event. It never blocks.
  5. Insert the access_requests row with status PENDING.
  6. Route approval: approval.RouteApproval — the recursive CTE finds the manager chain; the first manager gets a PENDING approvals row.

Step 2 — The decision

POST /access-requests/{id}/approve by the manager:

  • Self-decision is rejected (you can't approve your own request — remediation of audit finding C-2/C-3).
  • Authority check (checkDecisionAuthority): the decision-maker must be the routed pending approver; unrouted requests may be decided by any member and are audited authority: "UNROUTED".
  • The decision runs decide → conditional update to APPROVED → and immediately calls activateApproved in the same flow, so the request becomes ACTIVE and expires_at (from policy.MaxDurationMinutes, default 60 min) lands in one atomic UPDATE ... SET status='ACTIVE', expires_at=$4 WHERE id=$1 AND status=$2. APPROVED is never a resting state for human approvals — approval is activation.
  • Audit row ACCESS_GRANTED is appended to the hash chain.
  • The access.granted event is not published directly — it's written to outbox_events in the same logical unit, and the relay (5 s tick, FOR UPDATE SKIP LOCKED) publishes it to the zoth.access topic exchange with routing key access.granted.

Step 3 — Provisioning (the emitter)

cmd/emitter consumes from queue emitter.access (bound with keys access.granted/revoked/expired, prefetch 10):

  1. Dedup: PostgresDeduper.Seen — INSERT INTO processed_events ON CONFLICT DO NOTHING; zero rows affected = duplicate → Ack. Fail-closed on errors.
  2. Resolve: PlatformBridge.resolve loads user + resource + the resource's integration, verifies the event's tenant matches the integration's tenant (the bridge tenant check), decrypts credentials_encrypted (AES-256-GCM) — per call, never cached.
  3. Grant: platforms.Service.GrantAccess → zlink connector → e.g. GitHub: resolve username (via SearchUsers by email), then Repositories.AddCollaborator(owner, repo, username, {Permission: "push"}) for write access. Retry: 3 attempts, 500 ms base, exponential with jitter, 30 s cap.
  4. Errors after 5 retries → dead-letter to emitter.access.dlq for inspection.

Step 4 — Expiry (the loop)

RunExpiryLoop (ticker, 30 s): ListExpiredAccessRequests (WHERE status='ACTIVE' AND expires_at < now() LIMIT 100) → conditional transition to EXPIRED → access.expired outbox event → audit row. Race with a concurrent revoke? The conditional update makes one of them win with ErrConflict swallowed as "a concurrent transition beat us — safe with multiple replicas."

Step 5 — The revoke lands on GitHub

The emitter consumes access.expired exactly like a manual revoke: RemoveCollaborator. Idempotent by design — zlink.ErrNotFound from the platform ("already gone") is treated as success, because the goal state is achieved. This is the sentence that makes a security interviewer smile: "revocation converges, it doesn't error."

The 30-second version (memorize)

"User authenticates with a tenant-scoped JWT; creates a request against a tenant-owned resource; a priority-ordered JSONB policy matches it; the recursive-CTE manager chain routes approval; approval auto-activates with an expiry in one atomic conditional update; the grant event goes through a transactional outbox to RabbitMQ; the emitter dedupes, decrypts the integration's credentials, and calls GitHub/Azure/Notion through the zlink connector interface with retries and a DLQ; a 30-second loop expires access automatically; drift detection reconciles actual platform state every 5 minutes; and every step appends to a hash-chained audit log."

Bulletproof your resume

Say these answers aloud before moving on

These are the follow-ups an interviewer will actually ask about your projects.

01

Why does approval auto-activate instead of having a separate activation step?

Two reasons: human approvals are the moment authority is exercised, so making APPROVED a resting state adds a second race window (activate-vs-revoke) for zero benefit; and it lets expires_at be set in the same UPDATE that flips status — one atomic statement, no window where access is ACTIVE without an expiry (that was audit finding L-4's fix). The service retains a separate ActivateRequest method with an 'only approved can activate' guard for future API-driven activation, but no route exposes it today.

02

What if RabbitMQ is down when the approval happens?

Nothing is lost: the event is in outbox_events, committed with the business write. The relay ticks every 5 s, and its publishFn returns an error when the broker is unreachable, which aborts the batch without marking anything published — the backlog simply waits. The API even tolerates a broker-less boot. Broker availability affects provisioning latency, never correctness.

03

How do you know the GitHub grant actually happened?

Three layers: the emitter only Acks after zlink returns success (failures retry up to 5 times then DLQ); CheckAccess exists on the connector (IsCollaborator on GitHub) for direct verification; and the drift service periodically reconciles expected ACTIVE requests against actual platform state, publishing drift.detected events and audit rows when they disagree. Event-driven + reconciliation is the belt-and-suspenders pattern for side effects you don't fully control.