R12 · Week 12 Recall & Drill
Week 12 revision: resource modelling and idempotency keys, GraphQL's N+1 and cost limits, Protobuf field tags and RPC shapes, delegated authorisation versus identity, and Linux processes, memory, and file descriptors.
Original publication date unrecordedUpdated
Lesson preparation & details
Level: intermediate
By the end, you should be able to
- Model a domain as resources rather than actions, and design stable-key cursor pagination with explicit mutation and snapshot limits.
- Add idempotency keys to a creating endpoint so a client can retry a timed-out request safely.
- Reproduce the N+1 resolver problem and explain the batching fix, plus the production controls a public GraphQL endpoint requires.
- State the Protobuf compatibility rules and pick the right RPC shape among unary, server-stream, client-stream, and bidirectional.
- Explain why delegated authorisation does not establish identity, and decode only a synthetic token's claims and distinguish decoding from trusted protocol verification.
- Distinguish resident from virtual memory, and reason about descriptor exhaustion using a bounded ownership model and a read-only Linux diagnosis procedure.
Bring with you
Sources checked: . A source check does not change the publication or update date.
Editorial review: · What review means
In this article · 24 sections
Assessment scope
Attempt the recall prompts unaided, then work through the implementations and answer keys. The local programs use explicit fixtures and independent checks; product behavior is tied to named primary sources, not inferred from the toy models. Service deployment and production readiness are separate from this conceptual review. Timing budgets are adjustable practice suggestions.
Sessions covered
- S056 — REST API Design — Resources, Versioning, Idempotency
- S057 — GraphQL — Schema, Resolvers, N+1, When to Pick It
- S058 — gRPC & Protobuf — When RPC Wins
- S059 — AuthN & AuthZ — OAuth 2.0, OIDC, JWT
- S060 — OS Basics — Processes, Threads, Memory, FDs
By the end of this revision you can
- Model a domain as resources rather than actions, and design stable-key cursor pagination with explicit mutation and snapshot limits.
- Add idempotency keys to a creating endpoint so a client can retry a timed-out request safely.
- Reproduce the N+1 resolver problem and explain the batching fix, plus the production controls a public GraphQL endpoint requires.
- State the Protobuf compatibility rules and pick the right RPC shape among unary, server-stream, client-stream, and bidirectional.
- Explain why delegated authorisation does not establish identity, and decode only a synthetic token's claims and distinguish decoding from trusted protocol verification.
- Distinguish resident from virtual memory, and reason about descriptor exhaustion using a bounded ownership model and a read-only Linux diagnosis procedure.
90-min structure
| Block | Minutes | Practice |
|---|---|---|
| Warm-up recall | 5 | Summarize the five subjects without notes. |
| Blank-page reconstruction | 30 | Answer the fifteen prompts, then check the subject solutions. |
| Hands-on drill | 30 | Trace and run the bounded implementations; explain their limits. |
| Quiz + misconception | 15 | Defend the corrected answers with a failure case. |
| Gap analysis + preview | 10 | Record a discriminating retest and connect the next subjects. |
The 90-minute budget is pacing guidance, not a claim that full execution and every extension fit every learner's session.
Blank-page reconstruction · 30 min
S056 · REST API Design
- Contrast offset and cursor pagination in one sentence each, and say when you would choose which.
- Explain what an idempotency key does and how long the server must remember it.
- Name the versioning strategies and give a defensible argument for one.
S057 · GraphQL
- Explain what a resolver is and where the engine calls it.
- Describe the N+1 problem and the batching fix, in one sentence each.
- Name three cases where the simpler resource-oriented approach wins.
S058 · gRPC & Protobuf
- Say why the field tag number matters more than the field name.
- List the four RPC shapes with a real use case for each.
- Write the rules for adding a field to a deployed message without breaking existing clients.
S059 · Authentication & Authorisation
- Define authentication and authorisation in one sentence each.
- Walk through the authorization-code flow with the proof-key extension in five steps.
- Contrast server-side sessions with self-contained tokens on revocation and on multi-node scaling.
S060 · OS Basics
- State the one decisive difference between a process and a thread, and derive two consequences from it.
- Define resident and virtual memory, and say which one you alert on.
- Write the ordered commands you run to diagnose file-descriptor exhaustion.
Hands-on drill · 30 min
Work through retry ambiguity, resolver fan-out, token structure and bounded resource ownership. These are local teaching models, not a deployed payment service, cryptographic verifier or a deliberately exhausted host process.
Step 1 — idempotency keys (8 min)
The dangerous timeline is commit→response lost→retry. A record must bind the caller, operation key, canonical request fingerprint, outcome and retention policy. A different body with the same key is a conflict. This sequential model rejects expired records rather than silently executing an old ambiguous request again; actual APIs may document different expiry semantics. A database implementation must atomically claim a key and commit the business effect/result, or coordinate them with an outbox and downstream idempotency.
import copy
import hashlib
import json
records, orders = {}, []
def create_order(principal, key, body, now, lose_response=False):
if not key or set(body) != {"amount", "currency"}:
raise ValueError("key and exact order fields required")
if type(body["amount"]) is not int or body["amount"] <= 0 or body["currency"] != "EUR":
raise ValueError("positive integer cents in EUR required")
fingerprint=hashlib.sha256(json.dumps(body,sort_keys=True,separators=(",",":")).encode()).hexdigest()
identity=(principal,key)
if identity in records:
old=records[identity]
if old["hash"] != fingerprint: return 409, {"error":"different request"}
if now >= old["expires"]: return 409, {"error":"expired key; reconcile operation"}
return 200, copy.deepcopy(old["response"])
response={**copy.deepcopy(body),"id":len(orders)+1}
orders.append(copy.deepcopy(response))
records[identity]={"hash":fingerprint,"response":copy.deepcopy(response),"expires":now+60}
if lose_response: raise TimeoutError("committed; response lost")
return 201,response
body={"amount":4200,"currency":"EUR"}
try: create_order("alice","req1",body,0,True)
except TimeoutError: pass
status,replay=create_order("alice","req1",body,1)
assert status==200 and replay["id"]==1 and len(orders)==1
replay["amount"]=1
assert create_order("alice","req1",body,2)[1]["amount"]==4200
assert create_order("alice","req1",{"amount":9999,"currency":"EUR"},2)[0]==409
assert create_order("alice","req1",body,60)[0]==409 and len(orders)==1
# The same client-chosen key belongs to a different operation for another principal.
status,bob_order=create_order("bob","req1",body,2)
assert status==201 and bob_order["id"]==2 and len(orders)==2
assert create_order("alice","req1",body,3)[1]["id"]==1
assert create_order("bob","req1",body,3)[1]["id"]==2The 60 time units are a fixture choice, not a universal safe retention interval. Define the client's retry/reconciliation horizon and what happens after it. This model never deletes expired records: its tombstone prevents re-execution but its memory grows with distinct operations. Deleting the record would lose that protection; durable operation IDs, a bounded retry contract and a documented reconciliation path are production design decisions. principal must come from trusted authentication, not a caller-controlled body field, and this function still needs per-resource authorization in an API.
A deterministic business operation ID or a naturally idempotent resource replacement can also support retries; a client-supplied header is not the only possible design. This in-memory implementation is not crash-durable or concurrency-safe. Do not expose one user's replay to another user by key collision, and do not let request data overwrite the server's generated resource ID.
Step 2 — N+1 and batching (7 min)
# nplusone.py
users = {i: {"id": i, "name": f"user{i}"} for i in range(1, 6)}
posts = [{"id": p, "author_id": (p % 5) + 1, "title": f"post{p}"} for p in range(1, 21)]
calls = {"n": 0}
def fetch_user(uid): # naive resolver: one query per parent row
calls["n"] += 1
return users[uid]
def fetch_users(uids): # batched: one query for the whole level
calls["n"] += 1
return {u: users[u] for u in set(uids)}
calls["n"] = 0
naive = [{"title": p["title"], "author": fetch_user(p["author_id"])["name"]} for p in posts]
print(f"naive resolver: {calls['n']} backend calls for {len(posts)} posts")
calls["n"] = 0
loaded = fetch_users([p["author_id"] for p in posts])
batched = [{"title": p["title"], "author": loaded[p["author_id"]]["name"]} for p in posts]
print(f"batched loader: {calls['n']} backend calls for {len(posts)} posts")
assert naive == batched and calls["n"] == 1
print("same result:", naive == batched)Expected outcome: the naive version makes one call per post while the batched version makes a single call, and both produce identical output. The call count scales with parent rows, so a tiny fixture can hide the latency/load cost that appears at larger breadth. This example batches one author field level explicitly; a loader coalesces loads within its scheduling window, which does not necessarily coincide with an entire query-tree level.
The counters above measure 20 author lookups versus one batched author lookup; fetching the initial post list is not counted, so a real N+1 accounting would include it separately. Batching preserves input order and duplicates when distributing results; missing IDs need a defined error/null policy. Cache scope must respect authorization, often per request. Chunk limits, backend query size, nested dependencies and dynamic resolver paths mean cost does not simply become “depth only.” Query depth/complexity budgets, pagination caps, timeouts, authentication and per-resource authorization remain necessary. REST can be simpler for stable resource shapes, ordinary shared-cache use and clients that do not need flexible graph selection. GraphQL GET/persisted-query caching is possible; one POST endpoint does not imply that all HTTP caching is impossible or that application caching is free.
Step 3 — decode and verify a token by hand (7 min)
Preserve the learning objective while separating decoding from verification. The following unsigned synthetic string is deliberately not accepted as an identity credential. It demonstrates that base64url encoding provides no confidentiality. Do not paste a real bearer token into a public decoder or log its contents.
import base64,json
def encode(obj):
return base64.urlsafe_b64encode(json.dumps(obj).encode()).decode().rstrip("=")
def decode(segment):
return json.loads(base64.urlsafe_b64decode(segment+"="*(-len(segment)%4)))
synthetic=encode({"alg":"none"})+"."+encode({"sub":"fictional-user","aud":"example-api","exp":100})+"."
header,payload,signature=synthetic.split(".")
assert decode(header)["alg"]=="none" and signature==""
assert decode(payload)["sub"]=="fictional-user"
# Decoded claims remain attacker-controlled; no authenticated identity is returned.For real acceptance, use a maintained JWT/OIDC library, configured allowed algorithms and trusted issuer keys, validate issuer, intended audience, expiry/not-before under a clock policy, and apply the protocol's nonce/token-type requirements. Check the header's algorithm against server policy; ignoring the header completely is not the instruction. Key selection must not fetch an attacker-chosen URL. Reject malformed encodings and unsupported critical headers according to the library/protocol. The original sample's claim that deleting its algorithm check accepts alg:none was false: its independent HMAC comparison still rejected an empty signature. It also omitted issuer validation. A failed unsigned-token example is not a reproduced algorithm-confusion exploit.
OAuth delegates access; OIDC adds a defined authentication layer and ID token. In authorization-code+PKCE: the client generates a fresh verifier, its S256 challenge and state; redirects to the trusted authorization endpoint with challenge; receives a code on the registered redirect; checks state and exchanges code with verifier through the pending flow; validates the resulting tokens according to issuer, audience and protocol. An ID token is for its client, not a general API access token. PKCE protects code redemption, not every redirect/session attack. Server sessions support centralized revocation but need shared/session-routing state; self-contained access tokens can be locally checked yet normally remain valid until expiry unless an additional revocation/introspection mechanism exists. Authorization to a particular order is a separate check after authentication.
Step 4 — exhaust the descriptor table (8 min)
Do not lower this process's OS limits or open files until failure. Model ownership with three handles, then connect the model to read-only diagnosis. A leak is an acquired resource with no matching release, regardless of whether the resource is a file, socket or pipe.
class Handles:
def __init__(self,limit): self.limit,self.live,self.next_id=limit,set(),0
def acquire(self):
if len(self.live)>=self.limit: raise OSError("model: too many open handles")
handle=self.next_id; self.next_id+=1; self.live.add(handle); return handle
def close(self,handle): self.live.remove(handle)
pool=Handles(3)
opened=[pool.acquire() for _ in range(3)]
try: pool.acquire()
except OSError: pass
else: raise AssertionError("model limit ignored")
for handle in opened: pool.close(handle)
assert not pool.live
h=pool.acquire()
try: assert h in pool.live
finally: pool.close(h)
assert not pool.liveOn Linux, confirm the failing PID and error (EMFILE is per-process; ENFILE is system-wide), inspect /proc/PID/limits, count /proc/PID/fd, and inspect symlink targets or lsof -p PID under authorized access. Compare repeated samples and group file/socket/pipe types before blaming connection reuse. Those paths contain process information and permission boundaries; no live process was inspected here. A shell's ulimit -n is not necessarily the target service's limit. Restore correct ownership/closure and bounded pools; increasing limits alone can hide a leak.
Processes normally have separate virtual address spaces; threads share process memory and descriptors but have separate execution state/stacks. Shared memory between processes and synchronization among threads complicate the simple slogan. Virtual size includes mappings/reservations and is not resident physical usage. RSS also double-counts shared pages across processes and does not alone measure container pressure; consider proportional/private memory, cgroup limits, reclaim and faults. Descriptor pressure is a separate resource dimension.
Protocol compatibility and API transfer exercise
A Protobuf example retains tag 1 for an order ID, adds optional string note = 3, and reserves removed tag 2 and its former name. Never repurpose a deployed field number. Binary compatibility does not guarantee JSON/source compatibility: field names matter in JSON, and type/presence changes have specific rules. Unknown fields, default values and old-reader behavior need compatibility tests. Unary RPC suits one lookup, server streaming suits a result feed, client streaming suits upload chunks, and bidirectional streaming suits an interactive exchange; define cancellation, deadlines and flow control in each case.
For pagination, use (created_at,id) as a unique stable order and carry both boundary values in an opaque validated cursor. New rows before the boundary are not revisited, but updates to ordering keys, deletions and snapshot changes can still affect what is seen. If an exact consistent traversal is required, use a snapshot/version contract. URL, header or media-type API versioning are tradeoffs; prefer additive compatible changes when possible and define deprecation rather than treating a new path as a compatibility proof.
Exercise answer: after a timeout, the caller retries the same operation key and identical body, then reconciles the returned operation ID. It must not generate a fresh key simply because the response was lost. For an N+1 test, include repeated and missing author IDs and verify result correspondence as well as the backend-call budget. For auth, a successfully decoded payload without trusted verification is still untrusted data, even if its subject looks plausible.
Resource, pagination and resolver transfer solutions
POST /orders creates an order resource and returns its identifier; GET /orders/17 reads that resource; PUT /orders/17 replaces according to the API's full-representation contract. A cancellation can be represented as a cancellation resource/transition with business rules rather than pretending every operation is a CRUD field edit. Noun-shaped URLs alone do not establish authorization, HTTP semantics or idempotency. URL major-versioning is easy to route/document; header/media-type negotiation keeps paths stable but needs client/cache/tool support. Either requires actual compatibility tests and deprecation, not only a new version string.
# Stable unique ascending (created_at, id) keys; opaque cursor encoding omitted.
rows = [(1,10),(2,20),(2,21),(3,30),(4,40)]
def page_after(rows, cursor, size=2):
if size <= 0 or len(set(rows)) != len(rows):
raise ValueError('positive page size and unique composite keys required')
return sorted(row for row in rows if cursor is None or row > cursor)[:size]
first = page_after(rows,None)
assert first == [(1,10),(2,20)]
changed = [(0,5)] + [row for row in rows if row != (1,10)]
assert page_after(changed, first[-1]) == [(2,21),(3,30)]
assert page_after(rows, (4,40)) == []
# Offset's duplicate on an insertion before the boundary, without compensating delete:
assert sorted([(0,5)]+rows)[2:4] == [(2,20),(2,21)]Answer: the tiebreaker avoids dropping id 21 when two rows share time 2. Cursor traversal is stable against unrelated inserts/deletes before the immutable boundary; it is not a snapshot. A row whose sort key changes from before to after the boundary can reappear; a deleted unseen row disappears. Snapshot/version semantics are required for exact historical traversal. Large offsets often require scanning/skipping prior rows, while indexed keyset performance depends on the actual engine/query. Offset remains defensible for small stable lists or jumping to numbered pages when that behavior is required.
A GraphQL resolver obtains a field's value for its parent and arguments under schema execution. The N+1 code's author work is 20 calls versus 1 (plus an uncounted initial posts query). A request-scoped batch function must return one result per input key, in input order including duplicates, and a defined missing-key result:
def ordered_users(ids):
# One bounded batch over this local dictionary, not a database driver.
unique = {uid:users.get(uid) for uid in set(ids)}
return [unique[uid] for uid in ids]
assert [u['id'] if u else None for u in ordered_users([3,1,3,99])] == [3,1,3,None]
assert ordered_users([]) == []Do not share that cache across principals unless authorization is part of its key/contract. Flexible graph selection justifies GraphQL when clients need differing nested shapes; stable resource APIs, straightforward shared HTTP caching and simple clients can favor REST. Depth limits alone do not stop a shallow query requesting a million children; bound cardinality/complexity, backend time and authorization per object.
Actual synthetic signature-verification exercise
The earlier unsigned string is only a decoding exercise. This separate local test uses installed PyJWT and cryptography, an ephemeral RSA key generated in memory, and synthetic claims. No real identity provider, credential, login or key discovery is involved; nothing secret is printed or saved. It demonstrates cryptographic rejection plus issuer/audience/expiry policy using a maintained library, not a hand-rolled production verifier.
import time
import jwt
from cryptography.hazmat.primitives.asymmetric import rsa
private_key = rsa.generate_private_key(public_exponent=65537,key_size=2048)
public_key = private_key.public_key()
claims = {'iss':'https://issuer.example','aud':'lesson-api','sub':'fictional-user','exp':int(time.time())+300}
signed = jwt.encode(claims,private_key,algorithm='RS256')
def verified_subject(token):
return jwt.decode(token,public_key,algorithms=['RS256'],audience='lesson-api',issuer='https://issuer.example',options={'require':['iss','aud','sub','exp']})['sub']
assert verified_subject(signed) == 'fictional-user'
wrong_audience = jwt.encode({**claims,'aud':'other-api'},private_key,algorithm='RS256')
wrong_issuer = jwt.encode({**claims,'iss':'https://other.example'},private_key,algorithm='RS256')
expired = jwt.encode({**claims,'exp':0},private_key,algorithm='RS256')
head,body,sig = signed.split('.')
tampered = head+'.'+encode({**claims,'sub':'attacker'})+'.'+sig
for rejected in [synthetic,wrong_audience,wrong_issuer,expired,tampered]:
try: verified_subject(rejected)
except jwt.InvalidTokenError: pass
else: raise AssertionError('untrusted token accepted')
print('synthetic RS256 signature, issuer, audience and expiry checks passed')Attack mechanism: a vulnerable verifier may let the untrusted alg header change the verification operation or the role of a key. If it accepts none, it may skip signature verification; if it treats an RSA public key's known bytes as an HMAC secret after an RS256→HS256 switch, an attacker can compute that MAC. Neither follows merely from decoding the header: both require a verifier that permits the unsafe algorithm/key interpretation. The library exercise above neither enables that switch nor claims to reproduce it; the old sample's empty signature still fails its separate HMAC comparison.
Answer: pin supported algorithms from server configuration and ensure the header agrees; rejecting none is not the same as proving every algorithm-confusion attack impossible. The old HMAC sample still compared signatures after its header check was removed, so its claimed none success was incorrect. JWTs can be signed or encrypted in other profiles; the signed/unsigned encoded payloads here provide no confidentiality. Do not accept an OAuth token as an OIDC ID token just because it has a sub: issuer, audience, token type and protocol validation differ. Authentication establishes the recognized principal; authorization checks whether that principal may act on the particular order.
Protobuf evolution and RPC answers
syntax = "proto3";
package lesson;
message Order {
string id = 1;
reserved 2;
reserved "legacy_total";
optional string note = 3;
}
message OrderRequest { string id = 1; }
message OrderChunk { bytes content = 1; }
service Orders {
rpc Get(OrderRequest) returns (Order);
rpc Watch(OrderRequest) returns (stream Order);
rpc Upload(stream OrderChunk) returns (Order);
rpc Chat(stream OrderChunk) returns (stream OrderChunk);
}Compatibility answer: never change/reuse deployed field numbers; reserve deleted names and numbers. A new field with an unused tag can be binary wire-safe, but old readers do not suddenly use it and new readers must tolerate its absence; explicit optional presence can distinguish absent from present-default. Field renaming can remain binary-compatible yet break generated source or JSON names, so it is not universally compatible. Do not make unchecked type/presence changes; check both old/new readers and writers. The four RPC shapes are unary lookup, server-streamed changes, client-streamed chunks, and bidirectional conversation. The schema is documentation-reviewed, not compiled here; no generated stub interoperability is claimed. Clients must set realistic deadlines/cancellation and handle stream backpressure; gRPC does not universally supply a deadline automatically.
Descriptor diagnosis walkthrough and session tradeoffs
For an authorized Linux target PID, read (do not change) its limits, current descriptors and targets in this order:
# Replace 12345 with the explicitly selected service PID; read-only, not executed here.
pid=12345
cat "/proc/$pid/limits"
find "/proc/$pid/fd" -mindepth 1 -maxdepth 1 -type l | wc -l
ls -l "/proc/$pid/fd"
lsof -nP -p "$pid"Worked observation: if Open files soft limit is 1,024, descriptor counts rise 400→750→1,010 while load is steady and most new targets are sockets to one backend, investigate lifecycle/pooling/timeouts on that path. EMFILE on accept fits a per-process limit; ENFILE requires system-wide investigation. These counts race with open/close and permissions, and sockets alone do not prove a particular library leak. Fix ownership using context managers/finally and bounded pools; temporary limit changes require capacity and operational approval. No live PID or host limit is touched by this lesson.
Processes generally isolate address spaces, so one process writing ordinary memory does not mutate another's independent address space; threads share process memory/descriptors, so shared state requires synchronization and one descriptor close can affect sibling threads. Threads retain their own execution state/stacks. RSS reports resident pages, virtual size counts mappings/reservations, and cgroup/proportional/private-memory/fault metrics refine pressure diagnosis; descriptor exhaustion is independent of either number.
A server-side session identifier points to revocable state shared or routed among nodes; a self-contained access token can be locally verified on each node but normally needs expiry or an additional revocation/introspection mechanism for early invalidation. PKCE binds code redemption to the original verifier; OIDC adds the identity-token protocol, while state/redirect/token checks and per-resource authorization remain necessary.
Recall quiz — corrected contracts
- Cursor versus offset? The worked tie/insertion cases show the benefit; mutable sort keys and snapshot membership still need a contract.
- N+1 fix? Batch/deduplicate backend work and redistribute by input key; budgets, missing keys and authorization remain.
- Tag or name? Tags drive binary encoding; names still matter to source/JSON compatibility. Reserve both on removal.
- Algorithm confusion? Restrict allowed algorithms and trusted key use plus claims/protocol checks; decoding is not verification.
- Connections fail with CPU/RSS healthy? Check the actual process error, limits and descriptor trend/types before inferring a leak, and do not alert on virtual size alone.
Answer constraints and corrections
Authorization and token verification are separate. An unsigned-token rejection does not demonstrate a real algorithm-confusion exploit unless the verifier actually accepts it. Cursor pagination still needs an immutable ordering key and snapshot policy. No live auth flow, network resource exhaustion or unsafe cryptographic deployment is executed in this worksheet.
Gap analysis + next week preview · 10 min
- Did you test key/body conflict, principal isolation, expiry and the committed-but-lost response?
- Can you explain why the original unsigned-token exploit claim was false, and distinguish the executed local signature tests from an identity-provider integration?
- Can you reconstruct process-limit → descriptor trend → type/owner diagnosis without exhausting the host or assuming every leak is a socket?
Mark each answer unaided, hinted or not yet; select a failed assumption and use a new counterexample for the retest rather than memorizing one fixture. Keep the input contract, boundary case and independent expected answer next to any execution result.
Next week: S061–S065 cover TCP/DNS, L4/L7 load balancing, caching, CDNs and Docker. Descriptor ownership connects directly to sockets, pools and container processes.
Corrective source notes
- RFC 9110 HTTP semantics, June 2022 — Safe methods plus PUT and DELETE have idempotent intended effects; payment creation requires a separate retry protocol. Retrieved 2026-10-07; exact passage reviewed 2026-10-07.
- GraphQL October 2021 specification — A GraphQL field resolver receives the parent value and arguments and can perform asynchronous backend work. Retrieved 2026-10-07; exact passage reviewed 2026-10-07.
- dataloader — Batch results must align one-for-one with input keys, including missing results and order; batching does not remove this contract. Retrieved 2026-10-07; exact passage reviewed 2026-10-07.
- Language Guide (proto 3) | Protocol Buffers Documentation — Deployed Protobuf field numbers identify wire meaning and must not be repurposed; reserve retired numbers. Binary wire-safe evolution can still break application code; adding a field requires absent/default and old-reader consideration. Retrieved 2026-10-07; exact passage reviewed 2026-10-07.
- Core concepts, architecture and lifecycle | gRPC — gRPC defines unary, server-streaming, client-streaming and bidirectional RPCs with distinct message patterns. Retrieved 2026-10-07; exact passage reviewed 2026-10-07.
- gRPC deadlines — gRPC clients should set realistic explicit deadlines rather than assuming a default timeout. Retrieved 2026-10-07; exact passage reviewed 2026-10-07.
- RFC 9700 OAuth security BCP, January 2025 — PKCE is required for public clients under OAuth security best practice and binds authorization-code redemption to a transaction-specific challenge/verifier. Retrieved 2026-10-07; exact passage reviewed 2026-10-07.
- Final: OpenID Connect Core 1.0 incorporating errata set 2 — OIDC adds an authentication/identity layer on top of OAuth authorization. Retrieved 2026-10-07; exact passage reviewed 2026-10-07.
- RFC 8725 JWT best practices, February 2020 — JWT verification must restrict algorithms and check header agreement and key-algorithm use; accepting an attacker-selected algorithm is unsafe. Retrieved 2026-10-07; exact passage reviewed 2026-10-07.
- Usage Examples — PyJWT 2.15.1 documentation — JWT expiry requires current time before exp with explicitly allowed leeway; local library rejection is tested rather than assumed. Retrieved 2026-10-07; exact passage reviewed 2026-10-07.
- getrlimit(2) - Linux manual page — RLIMIT_NOFILE is one greater than the maximum descriptor number and exceeding it yields EMFILE; actual process limits matter. Retrieved 2026-10-07; exact passage reviewed 2026-10-07.
- Linux man-pages fork(2) — Forked processes normally have separate memory spaces; ordinary writes do not mutate the other process memory. Retrieved 2026-10-07; exact passage reviewed 2026-10-07.
These passages support the named propositions, not blanket product certification. The lesson-specific derivations and fixtures are separate evidence; runtime boundaries are stated beside each worked exercise.
Pause / Recall / Apply
Can you explain it without the page?
Close the example. Reconstruct the core idea, then change one assumption. Mark complete when you’re ready; you can always undo it.
Stored in this browser only. No account, no sync. Clearing browser data removes your record.