@causyne/agent 0.0.0-stage → 1.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +102 -0
- package/README.md +31 -2
- package/SECURITY.md +24 -0
- package/bin/causyne.js +13 -0
- package/docs/ARCHITECTURE.md +63 -0
- package/docs/PROVENANCE.md +29 -0
- package/docs/SECURITY_REVIEW.md +89 -0
- package/docs/THIRD_PARTY_NOTICES.md +49 -0
- package/native-skills/ai-engineering/context-window-management/SKILL.md +43 -0
- package/native-skills/ai-engineering/llm-evaluation/SKILL.md +46 -0
- package/native-skills/ai-engineering/model-cost-and-latency/SKILL.md +46 -0
- package/native-skills/ai-engineering/prompt-construction/SKILL.md +42 -0
- package/native-skills/ai-engineering/retrieval-augmented-generation/SKILL.md +42 -0
- package/native-skills/ai-engineering/structured-output-and-schemas/SKILL.md +45 -0
- package/native-skills/ai-engineering/tool-calling-agents/SKILL.md +43 -0
- package/native-skills/android/app-performance-profiling/SKILL.md +42 -0
- package/native-skills/android/background-work-scheduling/SKILL.md +47 -0
- package/native-skills/android/compose-state-and-recomposition/SKILL.md +43 -0
- package/native-skills/android/gradle-build-configuration/SKILL.md +41 -0
- package/native-skills/android/lifecycle-and-leaks/SKILL.md +46 -0
- package/native-skills/android/play-store-release/SKILL.md +43 -0
- package/native-skills/android/room-and-persistence/SKILL.md +40 -0
- package/native-skills/backend/authentication-and-sessions/SKILL.md +45 -0
- package/native-skills/backend/background-jobs-and-queues/SKILL.md +42 -0
- package/native-skills/backend/caching-strategy/SKILL.md +44 -0
- package/native-skills/backend/graceful-shutdown/SKILL.md +42 -0
- package/native-skills/backend/rate-limiting-and-quotas/SKILL.md +40 -0
- package/native-skills/backend/rest-api-design/SKILL.md +40 -0
- package/native-skills/backend/webhook-delivery-and-receipt/SKILL.md +43 -0
- package/native-skills/cloud/cost-optimisation/SKILL.md +46 -0
- package/native-skills/cloud/iam-least-privilege/SKILL.md +43 -0
- package/native-skills/cloud/infrastructure-as-code/SKILL.md +43 -0
- package/native-skills/cloud/multi-region-and-failover/SKILL.md +43 -0
- package/native-skills/cloud/serverless-patterns/SKILL.md +47 -0
- package/native-skills/data/data-quality-monitoring/SKILL.md +51 -0
- package/native-skills/data/etl-pipeline-reliability/SKILL.md +43 -0
- package/native-skills/data/exploratory-analysis/SKILL.md +43 -0
- package/native-skills/data/metric-definition/SKILL.md +42 -0
- package/native-skills/data/notebook-to-production/SKILL.md +43 -0
- package/native-skills/database/backup-and-recovery/SKILL.md +43 -0
- package/native-skills/database/connection-pooling/SKILL.md +42 -0
- package/native-skills/database/data-integrity-and-constraints/SKILL.md +42 -0
- package/native-skills/database/query-optimisation/SKILL.md +38 -0
- package/native-skills/database/safe-migrations/SKILL.md +47 -0
- package/native-skills/database/schema-design/SKILL.md +38 -0
- package/native-skills/database/transactions-and-isolation/SKILL.md +41 -0
- package/native-skills/design/colour-system-construction/SKILL.md +38 -0
- package/native-skills/design/design-direction-brief/SKILL.md +41 -0
- package/native-skills/design/motion-and-transitions/SKILL.md +38 -0
- package/native-skills/design/responsive-strategy/SKILL.md +39 -0
- package/native-skills/design/spacing-and-rhythm/SKILL.md +38 -0
- package/native-skills/design/type-scale-and-hierarchy/SKILL.md +39 -0
- package/native-skills/design/visual-qa-loop/SKILL.md +42 -0
- package/native-skills/devops/docker-multi-stage-builds/SKILL.md +60 -0
- package/native-skills/devops/github-actions-ci/SKILL.md +60 -0
- package/native-skills/devops/incident-triage/SKILL.md +38 -0
- package/native-skills/devops/kubernetes-deployment-hardening/SKILL.md +64 -0
- package/native-skills/devops/observability-baseline/SKILL.md +38 -0
- package/native-skills/devops/secret-management/SKILL.md +40 -0
- package/native-skills/devops/terraform-state-and-modules/SKILL.md +36 -0
- package/native-skills/devops/zero-downtime-deploys/SKILL.md +40 -0
- package/native-skills/git/bisect-regression-hunt/SKILL.md +43 -0
- package/native-skills/git/branch-strategy/SKILL.md +39 -0
- package/native-skills/git/commit-hygiene/SKILL.md +41 -0
- package/native-skills/git/history-recovery/SKILL.md +44 -0
- package/native-skills/git/large-repository-surgery/SKILL.md +46 -0
- package/native-skills/git/merge-conflict-resolution/SKILL.md +42 -0
- package/native-skills/media/cinematic-3d-shot/SKILL.md +104 -0
- package/native-skills/mobile/app-startup-optimisation/SKILL.md +41 -0
- package/native-skills/mobile/cross-platform-tradeoffs/SKILL.md +42 -0
- package/native-skills/mobile/deep-linking/SKILL.md +39 -0
- package/native-skills/mobile/mobile-storage-security/SKILL.md +45 -0
- package/native-skills/mobile/offline-first-sync/SKILL.md +42 -0
- package/native-skills/mobile/push-notifications/SKILL.md +38 -0
- package/native-skills/performance/algorithmic-complexity/SKILL.md +43 -0
- package/native-skills/performance/concurrency-and-parallelism/SKILL.md +42 -0
- package/native-skills/performance/load-testing/SKILL.md +42 -0
- package/native-skills/performance/memory-and-leak-hunting/SKILL.md +41 -0
- package/native-skills/performance/network-and-payload-efficiency/SKILL.md +42 -0
- package/native-skills/performance/profiling-methodology/SKILL.md +46 -0
- package/native-skills/python/async-correctness/SKILL.md +40 -0
- package/native-skills/python/cli-tool-construction/SKILL.md +44 -0
- package/native-skills/python/data-processing-pipelines/SKILL.md +38 -0
- package/native-skills/python/environment-isolation/SKILL.md +41 -0
- package/native-skills/python/packaging-and-distribution/SKILL.md +56 -0
- package/native-skills/python/pytest-suite-design/SKILL.md +39 -0
- package/native-skills/python/type-hints-and-static-checking/SKILL.md +42 -0
- package/native-skills/security/authorization-model/SKILL.md +42 -0
- package/native-skills/security/crypto-usage/SKILL.md +48 -0
- package/native-skills/security/dependency-supply-chain/SKILL.md +42 -0
- package/native-skills/security/input-validation-and-injection/SKILL.md +44 -0
- package/native-skills/security/secure-file-uploads/SKILL.md +46 -0
- package/native-skills/security/security-headers-and-csp/SKILL.md +44 -0
- package/native-skills/security/threat-modelling/SKILL.md +46 -0
- package/native-skills/testing/end-to-end-test-stability/SKILL.md +42 -0
- package/native-skills/testing/property-based-testing/SKILL.md +46 -0
- package/native-skills/testing/regression-test-from-a-bug/SKILL.md +41 -0
- package/native-skills/testing/snapshot-and-visual-regression/SKILL.md +42 -0
- package/native-skills/testing/test-doubles-and-mocking/SKILL.md +43 -0
- package/native-skills/testing/test-strategy-and-pyramid/SKILL.md +39 -0
- package/native-skills/testing/writing-good-assertions/SKILL.md +42 -0
- package/native-skills/web/accessible-components/SKILL.md +45 -0
- package/native-skills/web/api-client-resilience/SKILL.md +39 -0
- package/native-skills/web/bundle-size-reduction/SKILL.md +38 -0
- package/native-skills/web/core-web-vitals/SKILL.md +40 -0
- package/native-skills/web/css-layout-systems/SKILL.md +41 -0
- package/native-skills/web/form-validation-ux/SKILL.md +39 -0
- package/native-skills/web/react-state-architecture/SKILL.md +41 -0
- package/native-skills/web/ssr-hydration-correctness/SKILL.md +45 -0
- package/package.json +68 -4
- package/src/agentLoop.js +2458 -0
- package/src/appChannel.js +109 -0
- package/src/approvalGrants.js +66 -0
- package/src/auditLog.js +52 -0
- package/src/autoUpdate.js +12 -0
- package/src/capabilities.js +36 -0
- package/src/checkpoints.js +1199 -0
- package/src/cliMode.js +638 -0
- package/src/commands.js +805 -0
- package/src/compiledSkills.js +540 -0
- package/src/config.js +130 -0
- package/src/contextManager.js +498 -0
- package/src/cortex/autoCondenser.js +192 -0
- package/src/cortex/provisioner.js +27 -0
- package/src/cortex/vectorDb.js +447 -0
- package/src/designPalettes.js +99 -0
- package/src/devMode.js +6 -0
- package/src/embodimentSession.js +180 -0
- package/src/entrypoint.js +194 -0
- package/src/execSafe.js +21 -0
- package/src/gitSnapshot.js +378 -0
- package/src/mcp/jsonRpc.js +165 -0
- package/src/mcp/mcpClient.js +136 -0
- package/src/mcp/mcpRegistry.js +207 -0
- package/src/memory.js +62 -0
- package/src/memoryManager.js +124 -0
- package/src/modelCatalogue.js +132 -0
- package/src/modelIds.js +42 -0
- package/src/modelRouter.js +145 -0
- package/src/outputStore.js +246 -0
- package/src/perception.js +124 -0
- package/src/planCache.js +437 -0
- package/src/procSignal.js +32 -0
- package/src/providers/anthropic.js +408 -0
- package/src/providers/base.js +328 -0
- package/src/providers/dashscope-byok.js +86 -0
- package/src/providers/deepseek.js +80 -0
- package/src/providers/factory.js +37 -0
- package/src/providers/gemini.js +346 -0
- package/src/providers/moonshot.js +73 -0
- package/src/providers/muse.js +169 -0
- package/src/providers/ollama.js +104 -0
- package/src/providers/openai-compatible.js +84 -0
- package/src/providers/openai.js +20 -0
- package/src/providers/qwen-byok.js +76 -0
- package/src/providers/xai.js +73 -0
- package/src/provisioning/capabilityConsent.js +24 -0
- package/src/provisioning/capabilityProvisioner.js +271 -0
- package/src/provisioning/capabilityTools.js +29 -0
- package/src/reporting.js +373 -0
- package/src/sandbox.js +529 -0
- package/src/sessionStore.js +279 -0
- package/src/skillsManager.js +288 -0
- package/src/slopDetector.js +307 -0
- package/src/taskBuffer.js +45 -0
- package/src/taskShape.js +241 -0
- package/src/textDiff.js +150 -0
- package/src/tokenTracker.js +560 -0
- package/src/tool-scripts/ax_snapshot.applescript +39 -0
- package/src/tool-scripts/ax_snapshot.ps1 +10 -0
- package/src/tool-scripts/desktop_act.applescript +58 -0
- package/src/tool-scripts/resolve_automator.py +119 -0
- package/src/toolPacks.js +524 -0
- package/src/tools/archive.js +63 -0
- package/src/tools/astryxOracle.js +78 -0
- package/src/tools/axSnapshot.js +165 -0
- package/src/tools/blenderAutomator.js +349 -0
- package/src/tools/browserAutomator.js +252 -0
- package/src/tools/checkSystemSpecs.js +64 -0
- package/src/tools/clipboard.js +36 -0
- package/src/tools/codeSearchAst.js +101 -0
- package/src/tools/codeSearcher.js +230 -0
- package/src/tools/codeTools.js +262 -0
- package/src/tools/codebaseSearch.js +75 -0
- package/src/tools/createSkill.js +129 -0
- package/src/tools/deferredMcpSearch.js +246 -0
- package/src/tools/desktopAct.js +208 -0
- package/src/tools/desktopOpen.js +34 -0
- package/src/tools/dynamicLoader.js +44 -0
- package/src/tools/executeProceduralMedia.js +93 -0
- package/src/tools/fsDelete.js +51 -0
- package/src/tools/fsEdit.js +105 -0
- package/src/tools/fsFind.js +128 -0
- package/src/tools/fsList.js +77 -0
- package/src/tools/fsMove.js +57 -0
- package/src/tools/fsRead.js +87 -0
- package/src/tools/fsSearch.js +198 -0
- package/src/tools/fsWrite.js +41 -0
- package/src/tools/gameAiBehaviorTree.js +208 -0
- package/src/tools/gameAnimationRig.js +194 -0
- package/src/tools/gameAssetFetcher.js +372 -0
- package/src/tools/gameAssetHarvester.js +210 -0
- package/src/tools/gameAudioSynth.js +267 -0
- package/src/tools/gameBuilder.js +886 -0
- package/src/tools/gameCanvasAudit.js +101 -0
- package/src/tools/gameCinematicSequencer.js +151 -0
- package/src/tools/gameDataForge.js +124 -0
- package/src/tools/gameDialogueTreeGen.js +166 -0
- package/src/tools/gameEconomyBalancer.js +161 -0
- package/src/tools/gameEntitySpawner.js +207 -0
- package/src/tools/gameLoreForge.js +153 -0
- package/src/tools/gameQuestArcBuilder.js +152 -0
- package/src/tools/gameSceneBuilder.js +158 -0
- package/src/tools/gameShaderForge.js +181 -0
- package/src/tools/gameTilemapGenerator.js +164 -0
- package/src/tools/gameUiHudBuilder.js +292 -0
- package/src/tools/gameVfxEmitterGen.js +108 -0
- package/src/tools/gameWebRuntimeInspector.js +91 -0
- package/src/tools/generateMediaViaApi.js +358 -0
- package/src/tools/gitExec.js +54 -0
- package/src/tools/godotAutomator.js +437 -0
- package/src/tools/manageLocalAiDependencies.js +220 -0
- package/src/tools/memoryWrite.js +99 -0
- package/src/tools/multiEdit.js +107 -0
- package/src/tools/palmierAutomator.js +104 -0
- package/src/tools/pdfReader.js +112 -0
- package/src/tools/persistentShell.js +148 -0
- package/src/tools/printer.js +316 -0
- package/src/tools/procTerm.js +91 -0
- package/src/tools/processList.js +52 -0
- package/src/tools/readNativeSkill.js +39 -0
- package/src/tools/registry.js +277 -0
- package/src/tools/resolveAutomator.js +102 -0
- package/src/tools/screenshot.js +75 -0
- package/src/tools/subagentOrchestrator.js +305 -0
- package/src/tools/sysRun.js +125 -0
- package/src/tools/todoWrite.js +75 -0
- package/src/tools/toolDispatch.js +127 -0
- package/src/tools/toolForge.js +124 -0
- package/src/tools/updatePlan.js +88 -0
- package/src/tools/viewImage.js +82 -0
- package/src/tools/visionInspector.js +154 -0
- package/src/tools/webFetch.js +74 -0
- package/src/tools/webSearch.js +82 -0
- package/src/tui/ask.js +164 -0
- package/src/tui/banner.js +158 -0
- package/src/tui/block.js +152 -0
- package/src/tui/chatRenderer.js +953 -0
- package/src/tui/completion.js +424 -0
- package/src/tui/dialogs.js +606 -0
- package/src/tui/diff.js +296 -0
- package/src/tui/history.js +135 -0
- package/src/tui/home.js +217 -0
- package/src/tui/invocation.js +105 -0
- package/src/tui/keys.js +443 -0
- package/src/tui/lineEditor.js +256 -0
- package/src/tui/overlay.js +256 -0
- package/src/tui/prompt.js +1258 -0
- package/src/tui/selectList.js +335 -0
- package/src/tui/stage.js +330 -0
- package/src/tui/stageSurface.js +609 -0
- package/src/tui/statusLine.js +254 -0
- package/src/tui/text.js +345 -0
- package/src/tui/theme.js +393 -0
- package/src/tui/toolDisplay.js +176 -0
- package/src/tui/transcript.js +235 -0
- package/src/tui/viewport.js +337 -0
- package/src/uncertainty.js +271 -0
- package/src/userSkills.js +265 -0
- package/src/utils/cli_ui.js +0 -0
- package/src/utils/inquirer.js +12 -0
- package/src/utils/paths.js +37 -0
- package/src/version.js +38 -0
- package/src/vfeTracker.js +220 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: structured-output-and-schemas
|
|
3
|
+
description: Get reliable structured data out of a language model. Use when parsing model output fails, when JSON is malformed, or when output must feed another system.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Structured Output and Schemas
|
|
7
|
+
|
|
8
|
+
## The ordering of defences
|
|
9
|
+
|
|
10
|
+
1. Use the provider's structured output or tool-calling mode. This constrains generation and is a guarantee, not a request.
|
|
11
|
+
2. Where unavailable, ask for a schema and validate.
|
|
12
|
+
3. Repair or retry on failure.
|
|
13
|
+
4. Never regex your way through malformed output — that fails silently on the case you did not anticipate.
|
|
14
|
+
|
|
15
|
+
## Workflow
|
|
16
|
+
|
|
17
|
+
1. Define the schema first, in code.
|
|
18
|
+
- A single definition generates both the prompt's schema description and the runtime validator. Two hand-written copies drift, and the drift shows up as an unparseable response in production.
|
|
19
|
+
- **Done when:** the schema has one source and both uses derive from it.
|
|
20
|
+
|
|
21
|
+
2. Keep the schema shallow and named.
|
|
22
|
+
- Deep nesting and unions raise the failure rate. Descriptive field names do real work — `total_amount_usd` produces better output than `amt`, because the name is part of the instruction.
|
|
23
|
+
- **Done when:** nesting is shallow and every field name is self-explanatory.
|
|
24
|
+
|
|
25
|
+
3. Make the uncertain case representable.
|
|
26
|
+
- If the model may not know a value, the schema needs a null or an explicit unknown. Without one, it will invent a plausible value — the schema forces a shape, not a truth.
|
|
27
|
+
- **Done when:** every field the model might not know can express that.
|
|
28
|
+
|
|
29
|
+
4. Validate, then retry with the error.
|
|
30
|
+
- On a validation failure, send the error back and ask for a correction. This recovers most failures in one extra call. Bound the retries.
|
|
31
|
+
- **Done when:** a validation failure triggers a bounded repair loop.
|
|
32
|
+
|
|
33
|
+
5. Validate semantics, not just shape.
|
|
34
|
+
- A schema check confirms it is a number, not that it is the right number. Range checks, referential checks, and cross-field consistency belong after parsing.
|
|
35
|
+
- **Done when:** semantic checks run after schema validation.
|
|
36
|
+
|
|
37
|
+
6. Log every failure with the raw output.
|
|
38
|
+
- Failure patterns tell you what to fix in the schema or the prompt. Without the raw text there is nothing to learn from.
|
|
39
|
+
- **Done when:** raw output is retained on every parse failure.
|
|
40
|
+
|
|
41
|
+
## Verification
|
|
42
|
+
|
|
43
|
+
- The evaluation set parses successfully every time.
|
|
44
|
+
- A deliberately impossible request produces an explicit unknown, not an invention.
|
|
45
|
+
- A malformed response is repaired within the retry bound or fails cleanly.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tool-calling-agents
|
|
3
|
+
description: Build an agent that calls tools. Use when adding function calling, when an agent loops without progressing, or when tool results are mishandled.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Tool-Calling Agents
|
|
7
|
+
|
|
8
|
+
## The loop
|
|
9
|
+
|
|
10
|
+
Model proposes a call → the system validates and executes it → the result returns to the model → repeat until done. Every failure in an agent is in one of those four steps, and the validation step is the one most often skipped.
|
|
11
|
+
|
|
12
|
+
## Workflow
|
|
13
|
+
|
|
14
|
+
1. Describe tools for the model, not for a developer.
|
|
15
|
+
- The description is the model's only documentation. Say when to use it AND when not to. A parameter schema with descriptions and examples per field prevents most malformed calls.
|
|
16
|
+
- **Done when:** each tool says both when to use it and when not to.
|
|
17
|
+
|
|
18
|
+
2. Validate every call before executing.
|
|
19
|
+
- The arguments are model output — untrusted. Parse into a schema, check ranges, and check authorization for the CALLER, not for the agent. An agent running with broad privilege on behalf of a limited user is a privilege escalation.
|
|
20
|
+
- **Done when:** every call is schema-validated and authorized as the user.
|
|
21
|
+
|
|
22
|
+
3. Return errors as information, not as exceptions.
|
|
23
|
+
- A failed call should return a message the model can act on: what was wrong and what to try instead. Throwing ends the loop; a good error lets it recover, which is most of the value of the agent.
|
|
24
|
+
- **Done when:** every failure returns an actionable message.
|
|
25
|
+
|
|
26
|
+
4. Bound the loop from three directions.
|
|
27
|
+
- Maximum steps, maximum wall time, and maximum spend. Also detect repetition: the same call with the same arguments twice means the agent is stuck, and no further iteration will help.
|
|
28
|
+
- **Done when:** all three bounds exist and repetition is detected.
|
|
29
|
+
|
|
30
|
+
5. Keep the context from growing without limit.
|
|
31
|
+
- Tool results accumulate quickly. Summarise or drop old results, but never drop the original instruction. Truncate large outputs and offer a way to read more.
|
|
32
|
+
- **Done when:** a long run stays within the context window without losing the task.
|
|
33
|
+
|
|
34
|
+
6. Require approval for irreversible actions.
|
|
35
|
+
- Deleting, sending, paying, deploying. The human sees what will happen before it happens, and dismissing the question means no.
|
|
36
|
+
- **Done when:** every irreversible tool requires explicit confirmation.
|
|
37
|
+
|
|
38
|
+
## Verification
|
|
39
|
+
|
|
40
|
+
- A malformed call is rejected with a message the model can act on.
|
|
41
|
+
- A deliberately looping task terminates on the step or time bound.
|
|
42
|
+
- An irreversible action cannot execute without confirmation.
|
|
43
|
+
- A tool call cannot exceed the calling user's own permissions.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: app-performance-profiling
|
|
3
|
+
description: Diagnose slowness, jank, or battery drain in an Android app. Use when scrolling stutters, when startup is slow, or when users report heat or battery use.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Android Performance Profiling
|
|
7
|
+
|
|
8
|
+
## Measure on the device that hurts
|
|
9
|
+
|
|
10
|
+
A flagship hides everything. Profile on a mid-tier or low-tier device, on a release build with minification on. A debug build's numbers are not related to what users experience.
|
|
11
|
+
|
|
12
|
+
## Workflow
|
|
13
|
+
|
|
14
|
+
1. Name the symptom as a metric.
|
|
15
|
+
- Startup time, frame duration, memory ceiling, or wakeup count. "Feels slow" cannot be verified as fixed.
|
|
16
|
+
- **Done when:** there is a number and a baseline measurement.
|
|
17
|
+
|
|
18
|
+
2. For jank, find the long frames.
|
|
19
|
+
- The system tracing tool shows what ran during a dropped frame. The usual causes: work on the main thread, an unbounded layout pass, a bitmap decoded during scroll, or an over-broad recomposition.
|
|
20
|
+
- **Done when:** the dropped frames have a named cause in the trace.
|
|
21
|
+
|
|
22
|
+
3. For startup, separate the phases.
|
|
23
|
+
- Process creation, application initialisation, first activity, first frame. Library initialisers running eagerly at startup are the most common cause; move them behind lazy initialisation.
|
|
24
|
+
- **Done when:** each phase has a duration and the largest is identified.
|
|
25
|
+
|
|
26
|
+
4. Add a baseline profile.
|
|
27
|
+
- It ships ahead-of-time compilation hints for the startup and scroll paths, typically a double-digit percentage improvement for a small amount of work.
|
|
28
|
+
- **Done when:** a baseline profile is generated from a realistic journey and included in the release.
|
|
29
|
+
|
|
30
|
+
5. For memory, look at retention, not allocation.
|
|
31
|
+
- The heap dump shows what is held and by what. Bitmaps and cached lists dominate. Allocation churn matters less than a growing retained set.
|
|
32
|
+
- **Done when:** the retained heap returns to baseline after navigating away.
|
|
33
|
+
|
|
34
|
+
6. For battery, count wakeups and network calls.
|
|
35
|
+
- Every wakeup costs a radio transition. Batch network work and use scheduling constraints rather than timers.
|
|
36
|
+
- **Done when:** wakeups during idle are near zero.
|
|
37
|
+
|
|
38
|
+
## Verification
|
|
39
|
+
|
|
40
|
+
- The chosen metric has improved against the recorded baseline, with both numbers stated.
|
|
41
|
+
- Measurement was done on a release build on a mid-tier device.
|
|
42
|
+
- Frame duration stays under the budget through the affected interaction.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: background-work-scheduling
|
|
3
|
+
description: Schedule work that must survive app death on Android. Use when a task must run later, on a network connection, or periodically, and when a service or timer is being considered.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Android Background Work
|
|
7
|
+
|
|
8
|
+
## Choosing the mechanism
|
|
9
|
+
|
|
10
|
+
- **Coroutine in a ViewModel** — work that only matters while the screen is open. Dies with the screen, correctly.
|
|
11
|
+
- **WorkManager** — deferrable work that must complete even across app death and reboot. The default for anything durable.
|
|
12
|
+
- **Foreground service** — user-visible, user-initiated, ongoing work: playback, navigation, an active upload. Requires a notification and a declared type.
|
|
13
|
+
- **Alarm manager** — exact time triggers only, such as an alarm clock. Restricted, and misused for everything else.
|
|
14
|
+
|
|
15
|
+
Almost every "run this later" is WorkManager. Background execution limits mean a plain background service will simply be killed.
|
|
16
|
+
|
|
17
|
+
## Workflow
|
|
18
|
+
|
|
19
|
+
1. State the durability requirement.
|
|
20
|
+
- Must it survive the screen closing? The app being killed? A reboot? Each answer moves the choice down the list above.
|
|
21
|
+
- **Done when:** the mechanism is chosen with the requirement written down.
|
|
22
|
+
|
|
23
|
+
2. Declare constraints instead of polling.
|
|
24
|
+
- Network type, charging, storage, idle. The system batches constrained work across apps, which is why it costs far less battery than a timer.
|
|
25
|
+
- **Done when:** the work declares every condition it needs rather than checking and rescheduling.
|
|
26
|
+
|
|
27
|
+
3. Make the worker idempotent and re-runnable.
|
|
28
|
+
- It can be stopped and retried at any point. Partial progress must not corrupt state.
|
|
29
|
+
- **Done when:** running the worker twice produces one effect.
|
|
30
|
+
|
|
31
|
+
4. Deduplicate with unique work.
|
|
32
|
+
- Enqueue unique work with an explicit existing-work policy. Without it, a screen that enqueues on every open schedules the same job repeatedly.
|
|
33
|
+
- **Done when:** repeated enqueues produce one scheduled job.
|
|
34
|
+
|
|
35
|
+
5. Return the right result.
|
|
36
|
+
- Success, retry for a transient failure, failure for a permanent one. Returning failure for a network blip loses the work; returning retry for a validation error retries forever.
|
|
37
|
+
- **Done when:** each failure branch returns the matching result.
|
|
38
|
+
|
|
39
|
+
6. Respect the observed limits.
|
|
40
|
+
- Work is deferred, not immediate. Aggressive battery optimisation on some devices delays it further. Never promise the user a precise time.
|
|
41
|
+
- **Done when:** the UI does not claim work will happen at a specific moment.
|
|
42
|
+
|
|
43
|
+
## Verification
|
|
44
|
+
|
|
45
|
+
- The work completes after force-stopping the app and rebooting.
|
|
46
|
+
- Enqueuing repeatedly results in one job.
|
|
47
|
+
- Battery historian or the WorkManager inspector shows no unexpected wakeups.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: compose-state-and-recomposition
|
|
3
|
+
description: Write or fix Jetpack Compose UI. Use when a screen stutters, when state resets on rotation, or when a composable recomposes more than expected.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Compose State and Recomposition
|
|
7
|
+
|
|
8
|
+
## The model
|
|
9
|
+
|
|
10
|
+
A composable is a function of state. When state changes, Compose re-runs the functions that READ it. Performance problems are almost always the wrong scope reading the state, and correctness problems are almost always state stored in the wrong place.
|
|
11
|
+
|
|
12
|
+
## Workflow
|
|
13
|
+
|
|
14
|
+
1. Hoist state to the lowest common owner.
|
|
15
|
+
- A composable that owns state cannot be reused or previewed with different values. Take state as a parameter and emit events upward.
|
|
16
|
+
- **Done when:** every reusable composable is stateless and driven by parameters.
|
|
17
|
+
|
|
18
|
+
2. Survive configuration change and process death.
|
|
19
|
+
- `remember` survives recomposition only. `rememberSaveable` survives configuration change. A ViewModel with a saved-state handle survives process death.
|
|
20
|
+
- Choose by what the user would be upset to lose.
|
|
21
|
+
- **Done when:** rotating and restoring from background both preserve what the user expects.
|
|
22
|
+
|
|
23
|
+
3. Read state as late as possible.
|
|
24
|
+
- Reading a frequently changing value at the top of a screen recomposes the whole screen. Pass a lambda that reads it, and read inside the small composable that displays it — or use the graphics-layer overload for scroll-driven values so it never recomposes at all.
|
|
25
|
+
- **Done when:** a high-frequency value recomposes only the element that displays it.
|
|
26
|
+
|
|
27
|
+
4. Give list items stable keys.
|
|
28
|
+
- `items(list, key = { it.id })`. Without keys, inserting at the front recomposes every item and loses their internal state.
|
|
29
|
+
- **Done when:** inserting at the head of a list does not disturb the rest.
|
|
30
|
+
|
|
31
|
+
5. Keep side effects in effect handlers.
|
|
32
|
+
- A composable body may run many times, in any order, and be abandoned. Anything with a side effect belongs in `LaunchedEffect`, `DisposableEffect`, or `SideEffect`, keyed correctly.
|
|
33
|
+
- **Done when:** no network call, no logging and no mutation happens in a composable body.
|
|
34
|
+
|
|
35
|
+
6. Watch out for unstable parameters.
|
|
36
|
+
- A parameter Compose cannot prove stable forces recomposition every time. Prefer immutable types and persistent collections; mark types stable only when the guarantee is real.
|
|
37
|
+
- **Done when:** the compiler's stability report shows the hot composables as skippable.
|
|
38
|
+
|
|
39
|
+
## Verification
|
|
40
|
+
|
|
41
|
+
- Layout inspector recomposition counts stay flat during interaction that should not change a given element.
|
|
42
|
+
- Rotation preserves state.
|
|
43
|
+
- Scrolling a long list stays smooth on a mid-tier device.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gradle-build-configuration
|
|
3
|
+
description: Configure or repair an Android Gradle build. Use when a build fails after a dependency change, when build times are long, or when version numbers are scattered across files.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Android Gradle Configuration
|
|
7
|
+
|
|
8
|
+
## Workflow
|
|
9
|
+
|
|
10
|
+
1. Centralise versions in the version catalog.
|
|
11
|
+
- `gradle/libs.versions.toml` holds every version, library and plugin. Versions hardcoded across module build files drift, and a mismatch between two artifacts of the same library produces link errors that name neither.
|
|
12
|
+
- **Done when:** no module build file contains a literal version string.
|
|
13
|
+
|
|
14
|
+
2. Keep configuration out of the execution path.
|
|
15
|
+
- Reading a file or running a command at configuration time runs on every build, including `--dry-run`. Wrap it in a provider so it runs only when the task does.
|
|
16
|
+
- **Done when:** the configuration phase takes under a second on a warm build.
|
|
17
|
+
|
|
18
|
+
3. Enable the correctness features deliberately.
|
|
19
|
+
- Configuration cache and build cache both give large wins and both expose incorrectly declared tasks. Turn them on and fix what breaks — what breaks was already wrong.
|
|
20
|
+
- **Done when:** a second build with no changes reports mostly up-to-date or cached tasks.
|
|
21
|
+
|
|
22
|
+
4. Separate build types honestly.
|
|
23
|
+
- Debug: no minification, debuggable, a distinct application id suffix so it installs alongside release.
|
|
24
|
+
- Release: minification and resource shrinking on, and a signing config that reads from the environment or a properties file that is not committed.
|
|
25
|
+
- **Done when:** debug and release can be installed on the same device simultaneously.
|
|
26
|
+
|
|
27
|
+
5. Keep the minification rules with the code they protect.
|
|
28
|
+
- Consumer rules belong in the library module that needs them, not in the app's file. A library whose rules live in the app breaks for every other consumer.
|
|
29
|
+
- **Done when:** each module ships the rules its own code requires.
|
|
30
|
+
|
|
31
|
+
## Common failures
|
|
32
|
+
|
|
33
|
+
- **Duplicate class:** two dependencies bundling the same library. Resolve with a dependency constraint, never by excluding blindly.
|
|
34
|
+
- **Manifest merger conflict:** read the merged manifest report; add `tools:replace` only after understanding which value should win.
|
|
35
|
+
- **Method count:** enable multidex or, better, shrink what is actually unused.
|
|
36
|
+
|
|
37
|
+
## Verification
|
|
38
|
+
|
|
39
|
+
- A clean build succeeds and an incremental build is substantially faster.
|
|
40
|
+
- Release and debug variants both install and run.
|
|
41
|
+
- No version literal exists outside the catalog.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lifecycle-and-leaks
|
|
3
|
+
description: Fix an Android memory leak or lifecycle crash. Use when the app crashes after rotation, when a callback fires on a destroyed component, or when memory grows across navigations.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Android Lifecycle and Leaks
|
|
7
|
+
|
|
8
|
+
## What leaks
|
|
9
|
+
|
|
10
|
+
Anything long-lived holding a reference to anything short-lived. An activity is short-lived — it dies on every rotation. A singleton, a static field, a running coroutine, or a registered listener that outlives it keeps the whole view tree in memory.
|
|
11
|
+
|
|
12
|
+
## The recurring causes
|
|
13
|
+
|
|
14
|
+
1. A non-static inner class or a lambda capturing the activity, held by something longer-lived.
|
|
15
|
+
2. A listener or receiver registered and never unregistered.
|
|
16
|
+
3. A coroutine launched in a scope that is never cancelled.
|
|
17
|
+
4. A singleton constructed with the activity context instead of the application context.
|
|
18
|
+
5. A handler with a delayed message posted from a destroyed component.
|
|
19
|
+
|
|
20
|
+
## Workflow
|
|
21
|
+
|
|
22
|
+
1. Confirm the leak before theorising.
|
|
23
|
+
- LeakCanary in debug names the retaining chain. The chain is the answer; guessing from the symptom rarely is.
|
|
24
|
+
- **Done when:** you have the retaining reference chain.
|
|
25
|
+
|
|
26
|
+
2. Match the scope to the lifetime.
|
|
27
|
+
- `lifecycleScope` for work tied to the screen, `viewModelScope` for work tied to the ViewModel, and `repeatOnLifecycle(STARTED)` for collecting a flow only while visible. Collecting a flow in `onCreate` without it keeps collecting behind a locked screen.
|
|
28
|
+
- **Done when:** every coroutine and every collection is bound to a lifecycle.
|
|
29
|
+
|
|
30
|
+
3. Pair every registration with a removal.
|
|
31
|
+
- Register in `onStart`, unregister in `onStop`. Or use a `DefaultLifecycleObserver` so the pairing cannot be forgotten.
|
|
32
|
+
- **Done when:** every registration has a matching removal in the symmetric callback.
|
|
33
|
+
|
|
34
|
+
4. Pick the right context.
|
|
35
|
+
- Application context for anything stored beyond a screen. Activity context only for UI: inflating, theming, dialogs.
|
|
36
|
+
- **Done when:** no long-lived object holds an activity context.
|
|
37
|
+
|
|
38
|
+
5. Never hold a view reference past the view's life.
|
|
39
|
+
- In a fragment, null the binding in `onDestroyView`. The fragment outlives its view, and that gap is where the classic fragment leak lives.
|
|
40
|
+
- **Done when:** the binding is nulled and access after destruction is impossible.
|
|
41
|
+
|
|
42
|
+
## Verification
|
|
43
|
+
|
|
44
|
+
- LeakCanary reports no leak after rotating and navigating repeatedly.
|
|
45
|
+
- Memory returns to baseline after navigating away and forcing a collection.
|
|
46
|
+
- No crash after rotating during an in-flight network call.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: play-store-release
|
|
3
|
+
description: Prepare an Android release for the Play Store. Use when building a release bundle, when a submission is rejected, or when setting up signing.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Play Store Release
|
|
7
|
+
|
|
8
|
+
## Workflow
|
|
9
|
+
|
|
10
|
+
1. Build an app bundle, not an APK.
|
|
11
|
+
- The store requires it and it produces per-device downloads that are substantially smaller. Test the bundle with the bundle tool so you exercise what users will actually install, not a universal APK.
|
|
12
|
+
- **Done when:** a device-specific APK generated from the bundle installs and runs.
|
|
13
|
+
|
|
14
|
+
2. Get signing right and keep it recoverable.
|
|
15
|
+
- Enrol in Play App Signing so the upload key can be rotated if lost. Losing the signing key without it means the app can never be updated — a new listing is the only path.
|
|
16
|
+
- The keystore and its passwords never enter version control.
|
|
17
|
+
- **Done when:** signing config reads credentials from outside the repository and the release build is verified signed.
|
|
18
|
+
|
|
19
|
+
3. Match the version codes to the release.
|
|
20
|
+
- Version code strictly increasing, version name meaningful to a human. A duplicate code is rejected at upload.
|
|
21
|
+
- **Done when:** the code is higher than every previously uploaded build.
|
|
22
|
+
|
|
23
|
+
4. Test the minified build, not the debug one.
|
|
24
|
+
- Minification removes code reached only by reflection. Serialisation, dependency injection and any reflective library need keep rules, and the crash appears only in release.
|
|
25
|
+
- **Done when:** the release build has been exercised through every major flow on a real device.
|
|
26
|
+
|
|
27
|
+
5. Complete the declarations honestly.
|
|
28
|
+
- Data safety, permissions, target API level, and the declaration for any sensitive permission. These are the most common rejection causes, and an inaccurate data safety form is a policy violation rather than a formatting error.
|
|
29
|
+
- **Done when:** every declared item matches what the code actually does.
|
|
30
|
+
|
|
31
|
+
6. Stage the rollout.
|
|
32
|
+
- Internal testing, then a small percentage. Watch the crash rate before widening. A full rollout of a broken build reaches everyone before the first report arrives.
|
|
33
|
+
- **Done when:** the release starts below full rollout with a stated widening criterion.
|
|
34
|
+
|
|
35
|
+
7. Upload the mapping file.
|
|
36
|
+
- Without it every release crash report is unreadable.
|
|
37
|
+
- **Done when:** deobfuscation is confirmed on a test crash.
|
|
38
|
+
|
|
39
|
+
## Verification
|
|
40
|
+
|
|
41
|
+
- The bundle uploads and passes pre-launch checks.
|
|
42
|
+
- A minified build passes a full manual pass on a physical device.
|
|
43
|
+
- A test crash appears deobfuscated in the console.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: room-and-persistence
|
|
3
|
+
description: Add or fix local persistence on Android. Use when adding a database, when a schema change crashes on upgrade, or when database work runs on the main thread.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Room and Persistence
|
|
7
|
+
|
|
8
|
+
## Invariants
|
|
9
|
+
|
|
10
|
+
1. Every schema change needs a migration. Destructive fallback in a shipped app deletes the user's data.
|
|
11
|
+
2. No database access on the main thread. Room enforces this by default — that enforcement must not be disabled.
|
|
12
|
+
3. Queries return `Flow` so the UI updates when the data changes, rather than re-querying manually.
|
|
13
|
+
|
|
14
|
+
## Workflow
|
|
15
|
+
|
|
16
|
+
1. Commit the schema to version control.
|
|
17
|
+
- Set the schema export directory. The exported JSON is what makes migrations testable and what tells a reviewer the schema changed.
|
|
18
|
+
- **Done when:** the schema JSON is committed and updated with every version bump.
|
|
19
|
+
|
|
20
|
+
2. Write the migration with the version bump, in the same commit.
|
|
21
|
+
- A version bump without a migration crashes every existing install on first launch. Automated migrations handle simple cases; anything renaming or transforming data needs an explicit one.
|
|
22
|
+
- **Done when:** every version has a migration path from the previous one.
|
|
23
|
+
|
|
24
|
+
3. Test the migration against real data.
|
|
25
|
+
- `MigrationTestHelper` creates the old schema, inserts rows, runs the migration and asserts on the result. An untested migration is a hypothesis.
|
|
26
|
+
- **Done when:** a test migrates a populated old-version database and asserts the data survived.
|
|
27
|
+
|
|
28
|
+
4. Index what you filter and sort on.
|
|
29
|
+
- Room warns about a missing index on a foreign key. A full table scan on a list screen is invisible on a test database and unusable at ten thousand rows.
|
|
30
|
+
- **Done when:** every column in a WHERE or ORDER BY has an index, or its absence is justified.
|
|
31
|
+
|
|
32
|
+
5. Keep transactions narrow but complete.
|
|
33
|
+
- Multi-step writes are annotated as a transaction so a crash cannot leave a half-state. Do not hold a transaction open across a network call.
|
|
34
|
+
- **Done when:** no multi-step write can be interrupted into an inconsistent state.
|
|
35
|
+
|
|
36
|
+
## Verification
|
|
37
|
+
|
|
38
|
+
- Migration tests pass from every previous version.
|
|
39
|
+
- Strict mode reports no disk access on the main thread.
|
|
40
|
+
- A list screen with ten thousand rows scrolls smoothly.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: authentication-and-sessions
|
|
3
|
+
description: Implement login, sessions, or token handling. Use when authentication is being added, when tokens are stored in local storage, or when passwords are being hashed.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Authentication and Sessions
|
|
7
|
+
|
|
8
|
+
## Non-negotiables
|
|
9
|
+
|
|
10
|
+
1. Passwords are hashed with a memory-hard function — argon2id, or bcrypt where argon2 is unavailable. Never a general-purpose hash, salted or not: those are designed to be fast, which is precisely the wrong property.
|
|
11
|
+
2. Tokens live in `HttpOnly`, `Secure`, `SameSite` cookies. A token in local storage is readable by any script on the page, which turns any cross-site scripting bug into full account takeover.
|
|
12
|
+
3. Every authentication decision happens on the server. A client-side check is a display convenience, never a control.
|
|
13
|
+
|
|
14
|
+
## Workflow
|
|
15
|
+
|
|
16
|
+
1. Use a library or a provider.
|
|
17
|
+
- Authentication has many correct-looking implementations that are subtly broken. Prefer an established library or an identity provider over hand-rolling.
|
|
18
|
+
- **Done when:** the approach is named and its security assumptions are stated.
|
|
19
|
+
|
|
20
|
+
2. Get session lifetime right.
|
|
21
|
+
- Short-lived access token, longer-lived refresh token, and rotation on every refresh. Detect reuse of a rotated refresh token and revoke the whole family — that is the signal a token was stolen.
|
|
22
|
+
- **Done when:** replaying an old refresh token invalidates the session.
|
|
23
|
+
|
|
24
|
+
3. Make logout actually revoke.
|
|
25
|
+
- A stateless token cannot be revoked, so either keep server-side session state or maintain a denylist until expiry. Deleting the cookie alone leaves a valid token in the attacker's hands.
|
|
26
|
+
- **Done when:** a captured token stops working after logout.
|
|
27
|
+
|
|
28
|
+
4. Rate-limit and lock out.
|
|
29
|
+
- Per-account and per-address limits on login and on password reset. Without them, credential stuffing runs unimpeded.
|
|
30
|
+
- **Done when:** repeated failures are throttled and the throttle is tested.
|
|
31
|
+
|
|
32
|
+
5. Keep the error messages uniform.
|
|
33
|
+
- "Invalid email or password" for both cases. Distinguishing them enumerates accounts. The same applies to password reset: always report that a mail was sent.
|
|
34
|
+
- **Done when:** the response and its timing are the same for a known and an unknown account.
|
|
35
|
+
|
|
36
|
+
6. Regenerate the session identifier on privilege change.
|
|
37
|
+
- On login and on role elevation. Without this, an identifier planted before login remains valid after it.
|
|
38
|
+
- **Done when:** the identifier differs before and after login.
|
|
39
|
+
|
|
40
|
+
## Verification
|
|
41
|
+
|
|
42
|
+
- Password hashes use a memory-hard function with sensible parameters.
|
|
43
|
+
- The auth cookie has HttpOnly, Secure and SameSite set.
|
|
44
|
+
- A revoked refresh token fails and invalidates its family.
|
|
45
|
+
- Login responses are indistinguishable for unknown and wrong-password accounts.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: background-jobs-and-queues
|
|
3
|
+
description: Move work out of a request or add a job queue. Use when a request handler does slow work inline, when a job can be lost on restart, or when retries cause duplicate effects.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Background Jobs and Queues
|
|
7
|
+
|
|
8
|
+
## The two properties that matter
|
|
9
|
+
|
|
10
|
+
Delivery is AT-LEAST-ONCE in every real queue, so every job must be idempotent. And a job that fails forever must end up somewhere a human will look, or it will fail silently for months.
|
|
11
|
+
|
|
12
|
+
## Workflow
|
|
13
|
+
|
|
14
|
+
1. Move only what is safe to defer.
|
|
15
|
+
- The user must not need the result to complete their action. If they do, the work belongs in the request, and the fix is to make it faster.
|
|
16
|
+
- **Done when:** the request returns something meaningful without waiting for the job.
|
|
17
|
+
|
|
18
|
+
2. Enqueue an identifier, never a payload.
|
|
19
|
+
- Pass the record id and let the worker read the current state. A serialised payload is stale by the time it is processed, and it bloats the queue.
|
|
20
|
+
- **Done when:** job arguments are identifiers and primitives only.
|
|
21
|
+
|
|
22
|
+
3. Make every handler idempotent.
|
|
23
|
+
- Running twice must equal running once. Key side effects on a stable identifier, or record completion transactionally with the effect.
|
|
24
|
+
- **Done when:** running the same job twice produces one effect, verified by test.
|
|
25
|
+
|
|
26
|
+
4. Set retry policy per failure class.
|
|
27
|
+
- Transient (network, lock timeout): retry with exponential backoff and jitter. Permanent (validation, missing record): fail immediately to the dead-letter queue. Retrying a permanent failure burns capacity forever.
|
|
28
|
+
- **Done when:** the handler distinguishes the two and behaves differently.
|
|
29
|
+
|
|
30
|
+
5. Give the dead-letter queue an owner.
|
|
31
|
+
- Alert on its depth. A dead-letter queue nobody watches is a directory where work goes to be forgotten.
|
|
32
|
+
- **Done when:** a non-zero depth raises an alert.
|
|
33
|
+
|
|
34
|
+
6. Enqueue only after the transaction commits.
|
|
35
|
+
- Enqueuing inside a transaction that later rolls back leaves a job for a record that does not exist. Use a transactional outbox or an after-commit hook.
|
|
36
|
+
- **Done when:** a rolled-back transaction enqueues nothing.
|
|
37
|
+
|
|
38
|
+
## Verification
|
|
39
|
+
|
|
40
|
+
- Running a job twice produces one effect.
|
|
41
|
+
- A permanently failing job reaches the dead-letter queue without retrying indefinitely.
|
|
42
|
+
- Killing a worker mid-job leaves the job re-runnable.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: caching-strategy
|
|
3
|
+
description: Add or fix caching. Use when a cache serves stale data, when a cache miss storm overloads a database, or when caching is being introduced to fix a slow endpoint.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Caching Strategy
|
|
7
|
+
|
|
8
|
+
## Before adding a cache
|
|
9
|
+
|
|
10
|
+
A cache is a correctness risk taken deliberately in exchange for speed. Confirm the slowness is real, measured, and not a missing index — an index fixes the cause; a cache hides it and adds a staleness bug.
|
|
11
|
+
|
|
12
|
+
## Workflow
|
|
13
|
+
|
|
14
|
+
1. Decide the invalidation before the caching.
|
|
15
|
+
- Time-based expiry: simple, and the staleness window must be acceptable to the product, not just to you.
|
|
16
|
+
- Event-based: invalidate on write. Correct, and every write path must remember. One that forgets is a bug that only appears in production.
|
|
17
|
+
- **Done when:** the invalidation rule is written down and every write path honours it.
|
|
18
|
+
|
|
19
|
+
2. Build the key deliberately.
|
|
20
|
+
- Include everything the value depends on: identifier, version, locale, and the viewer's permissions. A cache key missing the permission dimension serves one user's data to another. This is the most damaging caching bug and it is common.
|
|
21
|
+
- **Done when:** two users with different permissions cannot share a key.
|
|
22
|
+
|
|
23
|
+
3. Prevent the stampede.
|
|
24
|
+
- When a hot key expires, every concurrent request misses and hits the origin at once. Use a lock so one request refills while others serve the stale value, or refresh ahead of expiry.
|
|
25
|
+
- **Done when:** expiring a hot key under load produces one origin call, not many.
|
|
26
|
+
|
|
27
|
+
4. Cache negatives too, briefly.
|
|
28
|
+
- A missing record queried repeatedly bypasses the cache entirely. Cache the absence with a short expiry.
|
|
29
|
+
- **Done when:** repeated lookups of a nonexistent key do not reach the origin each time.
|
|
30
|
+
|
|
31
|
+
5. Fail open.
|
|
32
|
+
- If the cache is unreachable the application serves from the origin, more slowly. A cache outage must not be an application outage.
|
|
33
|
+
- **Done when:** stopping the cache leaves the application working.
|
|
34
|
+
|
|
35
|
+
6. Measure the hit rate.
|
|
36
|
+
- A cache below roughly 80% hit rate is usually keyed wrongly or expiring too fast, and is adding complexity for little gain.
|
|
37
|
+
- **Done when:** the hit rate is instrumented and reported.
|
|
38
|
+
|
|
39
|
+
## Verification
|
|
40
|
+
|
|
41
|
+
- Hit rate is measured and reported.
|
|
42
|
+
- Stopping the cache degrades performance without breaking correctness.
|
|
43
|
+
- A write invalidates every affected key, verified by test.
|
|
44
|
+
- Two users with different permissions receive different data.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: graceful-shutdown
|
|
3
|
+
description: Make a service shut down cleanly. Use when a restart drops requests, when jobs are lost on deploy, or when a container takes the full termination grace period to exit.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Graceful Shutdown
|
|
7
|
+
|
|
8
|
+
## The sequence
|
|
9
|
+
|
|
10
|
+
Stop accepting new work → finish work in flight → release resources → exit. A process that exits on the signal drops every live request, and under a rolling deploy that is a burst of user-visible errors on every release.
|
|
11
|
+
|
|
12
|
+
## Workflow
|
|
13
|
+
|
|
14
|
+
1. Handle the termination signal, and the interrupt too.
|
|
15
|
+
- The orchestrator sends the termination signal and then waits a grace period before killing. Handle both signals; a container ignoring the signal always takes the full grace period.
|
|
16
|
+
- **Done when:** the process exits on signal in well under the grace period.
|
|
17
|
+
|
|
18
|
+
2. Fail readiness before you stop listening.
|
|
19
|
+
- Mark the instance unready first, then wait for the load balancer to notice — typically a few seconds — and only then stop accepting connections. Closing the listener immediately means the balancer is still routing to a closed socket.
|
|
20
|
+
- **Done when:** there is a deliberate delay between failing readiness and closing the listener.
|
|
21
|
+
|
|
22
|
+
3. Drain in-flight work with a deadline.
|
|
23
|
+
- Wait for open requests to finish, but not forever. A deadline shorter than the orchestrator's grace period, then force the close.
|
|
24
|
+
- **Done when:** shutdown completes within the deadline whether or not work finished.
|
|
25
|
+
|
|
26
|
+
4. Stop background workers cleanly.
|
|
27
|
+
- Stop pulling new jobs, let the current one finish or return it to the queue. A job abandoned mid-flight must be re-runnable — which is why job handlers are idempotent.
|
|
28
|
+
- **Done when:** no job is lost or half-applied when a worker stops.
|
|
29
|
+
|
|
30
|
+
5. Release resources in reverse order of acquisition.
|
|
31
|
+
- Close the server, then the queue consumers, then the database pool. Closing the pool first fails the requests still draining.
|
|
32
|
+
- **Done when:** shutdown logs show the reverse order and no error.
|
|
33
|
+
|
|
34
|
+
6. Never exit non-zero on a clean shutdown.
|
|
35
|
+
- The orchestrator reads a non-zero exit as a crash and may back off the whole deploy.
|
|
36
|
+
- **Done when:** a signalled shutdown exits 0.
|
|
37
|
+
|
|
38
|
+
## Verification
|
|
39
|
+
|
|
40
|
+
- A rolling restart under load produces zero failed requests.
|
|
41
|
+
- The process exits well inside the grace period.
|
|
42
|
+
- Stopping a worker mid-job leaves the job re-runnable.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rate-limiting-and-quotas
|
|
3
|
+
description: Add rate limiting or usage quotas to a service. Use when an endpoint can be called without bound, when one client can degrade service for others, or when abuse is suspected.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Rate Limiting and Quotas
|
|
7
|
+
|
|
8
|
+
## Workflow
|
|
9
|
+
|
|
10
|
+
1. Choose the dimension before the algorithm.
|
|
11
|
+
- Per user for fairness, per address for anonymous abuse, per endpoint for cost control, and globally for capacity protection. Most services need several at once.
|
|
12
|
+
- **Done when:** each limit names its dimension and the abuse it prevents.
|
|
13
|
+
|
|
14
|
+
2. Pick the algorithm to match the traffic.
|
|
15
|
+
- Token bucket: allows bursts up to the bucket size, refills steadily. The right default for user-facing APIs.
|
|
16
|
+
- Sliding window: smoother, no burst allowance, more storage.
|
|
17
|
+
- Fixed window: cheapest, but allows double the limit across a boundary.
|
|
18
|
+
- **Done when:** the choice is stated and the burst behaviour is acceptable.
|
|
19
|
+
|
|
20
|
+
3. Enforce where the state is shared.
|
|
21
|
+
- Per-instance counters mean the effective limit is multiplied by the instance count. Use a shared store with an atomic increment.
|
|
22
|
+
- **Done when:** scaling out does not change the effective limit.
|
|
23
|
+
|
|
24
|
+
4. Answer with the right status and headers.
|
|
25
|
+
- 429, with `Retry-After` and the `RateLimit` headers. A limit a client cannot see is a limit they will hit repeatedly.
|
|
26
|
+
- **Done when:** a limited response tells the client exactly when to retry.
|
|
27
|
+
|
|
28
|
+
5. Identify the client honestly.
|
|
29
|
+
- Behind a proxy, the connecting address is the proxy. Read the forwarded header only from a trusted proxy — otherwise a client can spoof it and bypass the limit entirely.
|
|
30
|
+
- **Done when:** the address is taken from a trusted hop and spoofing is tested.
|
|
31
|
+
|
|
32
|
+
6. Fail closed at the edge, open in the middle.
|
|
33
|
+
- If the limiter store is down, an authentication endpoint should reject; an internal read path should allow. State which each one does.
|
|
34
|
+
- **Done when:** the behaviour when the store is unavailable is decided per endpoint.
|
|
35
|
+
|
|
36
|
+
## Verification
|
|
37
|
+
|
|
38
|
+
- Exceeding the limit returns 429 with `Retry-After`.
|
|
39
|
+
- The limit holds when the service runs on several instances.
|
|
40
|
+
- A spoofed forwarded header does not raise the limit.
|