org-knowledge-layer 0.1.0__py3-none-any.whl
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.
- okl/__init__.py +12 -0
- okl/__main__.py +8 -0
- okl/bootstrap.py +83 -0
- okl/cli.py +484 -0
- okl/client.py +160 -0
- okl/core.py +223 -0
- okl/drift.py +119 -0
- okl/mcp_server.py +75 -0
- okl/scaffold/MANIFEST.md +59 -0
- okl/scaffold/ci/method-gates.yml +32 -0
- okl/scaffold/ci/okl-verify.yml +59 -0
- okl/scaffold/claude/agents/architecture-reviewer.md +41 -0
- okl/scaffold/claude/commands/check-rules.md +24 -0
- okl/scaffold/claude/commands/feature-spec.md +37 -0
- okl/scaffold/claude/rules/example-area.md +22 -0
- okl/scaffold/claude/skills/RECOMMENDED-COMPANIONS.md +40 -0
- okl/scaffold/claude/skills/encoding-loop/SKILL.md +48 -0
- okl/scaffold/claude/skills/verify-before-claiming/SKILL.md +56 -0
- okl/scaffold/evals/README.md +32 -0
- okl/scaffold/evals/cases.jsonl +1 -0
- okl/scaffold/evals/run_evals.py +109 -0
- okl/scaffold/gates/check-canon-size.sh +11 -0
- okl/scaffold/gates/check-doc-orphans.sh +19 -0
- okl/scaffold/gates/check-retractions.sh +22 -0
- okl/scaffold/gates/check-tombstones.sh +22 -0
- okl/scaffold/gates/run-gates.sh +31 -0
- okl/scaffold/hooks/hooks.json +16 -0
- okl/scaffold/hooks/stop-okl-encode.sh +78 -0
- okl/scaffold/hooks/userpromptsubmit-okl-check.sh +68 -0
- okl/scaffold/plugin/plugin.json +10 -0
- okl/scaffold/profiles/dotnet/README.md +12 -0
- okl/scaffold/profiles/dotnet/rules/architecture.md +55 -0
- okl/scaffold/profiles/dotnet/rules/messaging.md +31 -0
- okl/scaffold/profiles/dotnet/rules/performance-and-data.md +36 -0
- okl/scaffold/profiles/dotnet/rules/security.md +42 -0
- okl/scaffold/profiles/geospatial/README.md +6 -0
- okl/scaffold/profiles/geospatial/rules/geospatial-ml.md +38 -0
- okl/scaffold/profiles/python-rag/README.md +13 -0
- okl/scaffold/profiles/python-rag/rules/fastapi-backend.md +37 -0
- okl/scaffold/profiles/python-rag/rules/project-structure.md +28 -0
- okl/scaffold/profiles/python-rag/rules/rag-pipeline.md +73 -0
- okl/scaffold/profiles/react/README.md +18 -0
- okl/scaffold/profiles/react/rules/frontend.md +57 -0
- okl/scaffold/registries/RETRACTIONS.md +19 -0
- okl/scaffold/registries/tombstones.txt +7 -0
- okl/scaffold/root/CLAUDE.md +55 -0
- okl/scaffold/root/METHOD.md +64 -0
- okl/scaffold_cmd.py +110 -0
- okl/seed/dotnet-canon.json +489 -0
- okl/seed/dotnet-decisions.json +328 -0
- okl/seed/dotnet-defects.json +133 -0
- okl/seed/dotnet-review-surfaces.json +147 -0
- okl/seed/frontend-canon.json +116 -0
- okl/seed/geospatial-deeptime-defects.json +59 -0
- okl/seed/geospatial-defects.json +154 -0
- okl/seed/geospatial-enforcement-defects.json +121 -0
- okl/seed/geospatial-eval-defects.json +25 -0
- okl/seed/rag-defects.json +120 -0
- okl/seed/react-defects.json +45 -0
- okl/seed.py +55 -0
- okl/service.py +137 -0
- okl/store.py +432 -0
- org_knowledge_layer-0.1.0.dist-info/METADATA +475 -0
- org_knowledge_layer-0.1.0.dist-info/RECORD +67 -0
- org_knowledge_layer-0.1.0.dist-info/WHEEL +4 -0
- org_knowledge_layer-0.1.0.dist-info/entry_points.txt +2 -0
- org_knowledge_layer-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "The .NET-platform ARCHITECTURE DECISIONS import — extracted from docs/project-decisions.md (1279 lines), deduped against dotnet-canon.json. Decisions are recorded so they aren't silently reversed; org-scoped ones carry portable rationale (the honest trade-off tables travel, not just the verdicts). Not *-defects.json on purpose — review, then `okl seed seed/dotnet-decisions.json`.",
|
|
3
|
+
"nodes": [
|
|
4
|
+
{
|
|
5
|
+
"key": "pd_microservices_vs_modular_monolith",
|
|
6
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
7
|
+
"found_by": "project-decisions.md §2",
|
|
8
|
+
"tags": "method",
|
|
9
|
+
"title": "Default new systems to a modular monolith; go microservices-first only when distribution is the deliverable",
|
|
10
|
+
"body": "the .NET platform chose 5 independently-deployable services (own DB each, gRPC sync, RabbitMQ async) explicitly because the project's purpose is demonstrating distributed patterns in their natural habitat — real sagas, a real dual-write problem, real network hops. The doc's honest answer: for a real greenfield production system, 'start with schema-level isolation in one process' is correct. Accepted costs were itemized: 5x deployment surface, real network calls, cross-service eventual consistency, saga complexity."
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"key": "pd_minimal_apis_over_controllers",
|
|
14
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
15
|
+
"found_by": "project-decisions.md §4",
|
|
16
|
+
"tags": "dotnet",
|
|
17
|
+
"title": "Minimal APIs over MVC controllers — unless you need heavy model binding or attribute-composed filter stacks",
|
|
18
|
+
"body": "Every HTTP endpoint is a Minimal API endpoint: less ceremony, ~10-15% throughput gain on simple endpoints, composable route groups, native OpenAPI metadata, and thin-shim endpoints that just dispatch to handlers so business logic has nowhere to leak. Controllers remain the right pick for heavy model binding (form posts, multipart, custom binders) or large attribute-composed filter/auth conventions — the decision names those exceptions explicitly."
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"key": "pd_url_segment_versioning",
|
|
22
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
23
|
+
"found_by": "project-decisions.md §5",
|
|
24
|
+
"tags": "dotnet",
|
|
25
|
+
"title": "Version APIs in the URL segment, not headers — visibility beats REST purity",
|
|
26
|
+
"body": "URL-segment versioning wins because versions are visible in raw logs, HTTP caches key on URL (header versioning needs fragile Vary config), routes are curl/browser-debuggable, and versioned route groups map directly to versioned OpenAPI docs. The 'header versioning is more RESTful' argument is academic — Stripe, GitHub, and AWS all use URL versioning. v2 ships as a side-by-side route group without touching v1 handlers."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"key": "pd_require_explicit_api_version",
|
|
30
|
+
"type": "Rule", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
31
|
+
"found_by": "project-decisions.md §5",
|
|
32
|
+
"tags": "dotnet",
|
|
33
|
+
"title": "Require the API version segment — never assume a default for unversioned calls",
|
|
34
|
+
"body": "An unversioned call gets a 400 instead of silently routing to v1. If unversioned calls implicitly hit 'current', the day v2 ships with a behavior change every unversioned caller silently migrates, turning the rollout into a debugging nightmare. Forcing callers to declare the version they expect makes future migrations opt-in and observable.",
|
|
35
|
+
"symptom": "AssumeDefaultVersionWhenUnspecified=true, or clients calling unversioned routes",
|
|
36
|
+
"fix": "require the version segment; unversioned calls fail fast"
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"key": "pd_openapi_dev_only",
|
|
40
|
+
"type": "Rule", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
41
|
+
"found_by": "project-decisions.md §6",
|
|
42
|
+
"tags": "security,dotnet",
|
|
43
|
+
"title": "OpenAPI specs and interactive API docs are dev-only — a public spec is reconnaissance gold",
|
|
44
|
+
"body": "OpenAPI output reveals the full attack surface: endpoints, schemas, and auth requirements. All spec routes and the docs UI are registered only in Development; if production spec access were ever needed it would go behind authenticated admin routes, never public.",
|
|
45
|
+
"symptom": "a spec route or API-docs UI reachable in a non-dev environment",
|
|
46
|
+
"fix": "gate spec + docs registration on the environment; authenticate any production exposure"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"key": "pd_scalar_over_swagger",
|
|
50
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
51
|
+
"found_by": "project-decisions.md §6",
|
|
52
|
+
"tags": "dotnet",
|
|
53
|
+
"title": "OpenAPI via the first-party generator (Microsoft.AspNetCore.OpenApi) with Scalar UI, serving JSON and YAML",
|
|
54
|
+
"body": "Scalar over Swashbuckle/Swagger UI: cleaner UX, dark mode, iframe-free try-it-out, and first-class integration with the first-party generator that replaces Swashbuckle for new projects. Both JSON and YAML are served because some tooling (Spectral, docs embedding) prefers YAML and the cost is one extra route per service."
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"key": "pd_established_idp_never_handroll_auth",
|
|
58
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
59
|
+
"found_by": "project-decisions.md §7",
|
|
60
|
+
"tags": "security,dotnet",
|
|
61
|
+
"title": "Delegate identity to an established open-source IdP — never hand-roll JWT minting",
|
|
62
|
+
"body": "Keycloak over Cognito (cloud lock-in) and IdentityServer (went commercial as Duende): battle-tested OAuth2/OIDC compliance, self-hostable, realm-based multi-tenancy, and the exact same IdP container locally and in prod. Hand-rolled token minting was rejected outright — cryptographic security primitives implemented in business code is how breaches happen. App-side validation stays trivial and canonical via the framework's JWT bearer support."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"key": "pd_keycloak_realm_reimport",
|
|
66
|
+
"type": "Rule", "scope": "repo", "repo": "dotnet-microservices", "verified": true,
|
|
67
|
+
"found_by": "project-decisions.md §7",
|
|
68
|
+
"tags": "security",
|
|
69
|
+
"title": "Keycloak realm-config changes only take effect after a re-import — fresh container and volume locally",
|
|
70
|
+
"body": "Edits to auth-realm.json do not hot-apply to a running Keycloak; the realm is read only at import time. To pick up token-policy or client changes, recreate the container with a fresh volume so the realm re-imports.",
|
|
71
|
+
"symptom": "realm JSON changed but the running Keycloak still shows old token lifetimes or client settings",
|
|
72
|
+
"fix": "tear down the Keycloak container and its volume so the realm re-imports on next boot"
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"key": "pd_authz_adapts_at_endpoint_boundary",
|
|
76
|
+
"type": "Rule", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
77
|
+
"found_by": "project-decisions.md §8",
|
|
78
|
+
"tags": "security,dotnet",
|
|
79
|
+
"title": "Principal-vs-resource authorization adapts at the transport edge — handlers never see HttpContext or claims",
|
|
80
|
+
"body": "The JWT principal and HttpContext are HTTP concepts; application-layer handlers must not know about them. The endpoint compares the authenticated user's claim to the requested resource owner (or passes it in as a domain-meaningful value) before the command crosses the layer boundary, so the domain and application layers stay transport-agnostic.",
|
|
81
|
+
"symptom": "a handler reaching into HttpContext/ClaimsPrincipal",
|
|
82
|
+
"fix": "adapt auth context to domain values at the endpoint; dispatch clean commands/queries"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"key": "pd_three_validation_layers",
|
|
86
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
87
|
+
"found_by": "project-decisions.md §9",
|
|
88
|
+
"tags": "dotnet",
|
|
89
|
+
"title": "Validation is layered three deep — pipeline shape checks, domain invariants, state-transition guards — deliberately, not redundantly",
|
|
90
|
+
"body": "Pipeline validation (FluentValidation before any handler) catches bad input shape; entity factories throw on invariant violations even if pipeline validation somehow passed — defense in depth, not duplication; state-transition methods throw for context-dependent rules where the same input is valid or invalid depending on current state. Each layer catches a class of error the others structurally cannot."
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"key": "pd_wolverine_middleware_order",
|
|
94
|
+
"type": "Rule", "scope": "repo", "repo": "dotnet-microservices", "verified": true,
|
|
95
|
+
"found_by": "project-decisions.md §9",
|
|
96
|
+
"tags": "dotnet,messaging",
|
|
97
|
+
"title": "Wolverine pipeline order is load-bearing: validation → context propagation → transactions",
|
|
98
|
+
"body": "Validation runs before ContextPropagationMiddleware opens the logger scope so 400 rejections don't pollute scopes or traces, and AutoApplyTransactions comes last so handlers run inside a transaction with correlation already restored. The handler only ever sees valid messages; reordering silently changes observability and rejection behavior.",
|
|
99
|
+
"symptom": "rejected-command noise in logger scopes, or handlers running before correlation restore",
|
|
100
|
+
"fix": "keep the canonical middleware order in Program.cs"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"key": "pd_rfc7807_global_handler_traceid",
|
|
104
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
105
|
+
"found_by": "project-decisions.md §10",
|
|
106
|
+
"tags": "dotnet",
|
|
107
|
+
"title": "One global exception handler maps exception types to RFC 7807 statuses, always carrying the trace ID",
|
|
108
|
+
"body": "A single shared handler switch-maps exception types to statuses (validation → 400 with per-field errors, concurrency conflict → 409, argument → 400, invalid-operation → 409, everything else → generic 500). Every error response embeds the current trace ID so a user-reported error links straight to full server-side logs — the pairing is what makes RFC 7807 + tracing a complete observability story. Endpoints stay thin: no per-endpoint try/catch, one switch arm per new exception type, one error shape for the frontend."
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"key": "pd_rate_limit_expensive_endpoints",
|
|
112
|
+
"type": "Rule", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
113
|
+
"found_by": "project-decisions.md §11",
|
|
114
|
+
"tags": "security,dotnet",
|
|
115
|
+
"title": "Rate-limit the endpoints backed by expensive unindexable queries — before one client can DOS the database",
|
|
116
|
+
"body": "A search endpoint running a leading-wildcard LIKE cannot use an index well, so one scraping client could take down the database; a named fixed-window policy (30 req/10s, no queueing, 429 on reject) applies to exactly that endpoint. The pattern: declare named policies centrally, apply selectively at the endpoint, prioritize search, payment, and auth surfaces. Real users never notice; abusive clients hit the wall.",
|
|
117
|
+
"symptom": "an unauthenticated or expensive-query endpoint with no rate limit",
|
|
118
|
+
"fix": "named central policies, applied selectively; search/payment/auth first"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"key": "pd_transport_decision_tree",
|
|
122
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
123
|
+
"found_by": "project-decisions.md §12",
|
|
124
|
+
"tags": "dotnet,messaging",
|
|
125
|
+
"title": "One transport per situation: REST for the frontend only, gRPC for sync inter-service, events for fan-out",
|
|
126
|
+
"body": "REST (versioned JSON, RFC 7807) is exclusively frontend-to-service; inter-service calls never go over REST. gRPC serves synchronous inter-service queries needing a definitive answer before proceeding (validate product + reserve stock during order placement) — binary payloads ~3-10x smaller than JSON, multiplexing, proto-enforced contracts, deadline propagation. Bus events serve fire-and-forget workflow notifications with multiple subscribers and queue-until-recovery resilience. Making the tree explicit stops ad-hoc transport choices."
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"key": "pd_wolverine_over_mediatr_masstransit",
|
|
130
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
131
|
+
"found_by": "project-decisions.md §13",
|
|
132
|
+
"tags": "dotnet,messaging",
|
|
133
|
+
"title": "Wolverine replaces MediatR+MassTransit — one MIT framework covering both, dodging two license transitions",
|
|
134
|
+
"body": "MediatR went commercial/sponsorware in 2024; MassTransit v9 goes commercial Q1 2026 with v8 OSS maintenance ending after 2026 — the traditional two-library stack becomes two paid dependencies. Wolverine (MIT) covers both concerns with one handler shape, a built-in transactional outbox, cascading messages, convention-based discovery, and a middleware pipeline. Accepted costs: far smaller community (~50x fewer downloads — you're sometimes the first to ask), a startup codegen phase, steeper learning curve, weaker state-machine sagas — acceptable because the saga is choreographed."
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"key": "pd_wolverine6_runtime_compilation_split",
|
|
138
|
+
"type": "Defect", "scope": "repo", "repo": "dotnet-microservices", "verified": true,
|
|
139
|
+
"found_by": "project-decisions.md §13 Wolverine 5→6 upgrade notes (GH-2876)",
|
|
140
|
+
"tags": "dotnet,messaging",
|
|
141
|
+
"title": "Wolverine 6 split Roslyn codegen out of core — Dynamic mode throws at startup without WolverineFx.RuntimeCompilation",
|
|
142
|
+
"body": "In the 5.39.3→6.8.0 upgrade, core WolverineFx stopped shipping the Roslyn compiler, so the default TypeLoadMode.Dynamic fails at host startup. The fix — referencing WolverineFx.RuntimeCompilation, which auto-registers — lives in ServiceDefaults so it flows to every service. Deferred production alternative: pre-generated static codegen, dropping the runtime Roslyn dependency for faster cold start and AOT.",
|
|
143
|
+
"symptom": "host throws at startup: no assembly generator (Roslyn) registered",
|
|
144
|
+
"fix": "reference WolverineFx.RuntimeCompilation via ServiceDefaults, or pre-generate static codegen"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"key": "pd_wolverine6_service_location_policy",
|
|
148
|
+
"type": "Defect", "scope": "repo", "repo": "dotnet-microservices", "verified": true,
|
|
149
|
+
"found_by": "project-decisions.md §13 Wolverine 5→6 upgrade notes",
|
|
150
|
+
"tags": "dotnet,messaging",
|
|
151
|
+
"title": "Wolverine 6 rejects codegen service-location fallback by default — factory-registered dependencies break startup",
|
|
152
|
+
"body": "Generated handler code either inlines dependencies or falls back to a container lookup; 6.x flipped ServiceLocationPolicy to NotAllowed, so dependencies that can't be inlined (interfaces with factory registrations, pooled DbContexts) fail at startup. This is a codegen-strategy concern, not the service-locator anti-pattern — handlers still use constructor injection. Fixed via a shared extension setting AlwaysAllowed, called by every service.",
|
|
153
|
+
"symptom": "startup rejection after a Wolverine major bump when a handler dependency has a factory or pooled registration",
|
|
154
|
+
"fix": "apply the shared service-location extension (AlwaysAllowed) from ServiceDefaults"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
"key": "pd_nonhandler_outbox_idbcontextoutbox",
|
|
158
|
+
"type": "Defect", "scope": "repo", "repo": "dotnet-microservices", "verified": true,
|
|
159
|
+
"found_by": "project-decisions.md §13 (PaymentRecoveryJob; proven by PaymentRecoveryAtomicityTests)",
|
|
160
|
+
"tags": "dotnet,messaging",
|
|
161
|
+
"title": "Non-handler code publishes through IDbContextOutbox — the hand-rolled transaction+publish wrap silently stopped being atomic on Wolverine 6",
|
|
162
|
+
"body": "A recovery job publishing outside the handler pipeline has no method-injected enlisted context; its old manual BeginTransaction→Publish→SaveChanges→Commit sequence silently lost atomicity on 6.x. Current pattern: enroll the DbContext in IDbContextOutbox, publish through it, then SaveChangesAndFlushMessagesAsync() — envelope staged, entity saved, both committed in one transaction. The guarantee is proven by a rollback test, not config inspection.",
|
|
163
|
+
"symptom": "a background job's entity write rolls back but its event was already dispatched (or vice versa)",
|
|
164
|
+
"fix": "publish via IDbContextOutbox + SaveChangesAndFlushMessagesAsync; never hand-roll transaction-plus-publish outside the pipeline"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"key": "pd_major_upgrade_runtime_breaks",
|
|
168
|
+
"type": "Claim", "scope": "org", "repo": "dotnet-microservices", "verified": true, "status": "live",
|
|
169
|
+
"found_by": "project-decisions.md §13 (Wolverine 5→6: three runtime breaks behind a green build)",
|
|
170
|
+
"tags": "method",
|
|
171
|
+
"title": "A source-compatible build on a major dependency upgrade proves nothing — runtime breaks hide behind green compiles",
|
|
172
|
+
"body": "The Wolverine 5→6 major bump compiled cleanly yet carried three runtime breaking changes: missing Roslyn codegen, a flipped service-location default, and silently lost transaction enlistment for publishes. All three were caught only by the integration suite; the third would have been a production data-loss bug. Treat a green build on a major bump as the start of verification: run integration tests exercising startup, codegen, and transactional behavior, and write up the breaks so the next bump doesn't re-derive them."
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"key": "pd_rollback_test_write_then_publish",
|
|
176
|
+
"type": "Rule", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
177
|
+
"found_by": "project-decisions.md §13 (the test that caught the Wolverine 6 non-enlistment bug)",
|
|
178
|
+
"tags": "messaging,method",
|
|
179
|
+
"title": "Prove outbox atomicity with rollback tests — force the commit to fail and assert no event escaped",
|
|
180
|
+
"body": "A save-interceptor throws when the entity commits; the test asserts both that the entity rolled back and that no event was dispatched. On the broken code the order rolled back but the event had already been sent — exactly the failure that config-level reasoning missed three times. Every write-then-publish path deserves a rollback test, because outbox atomicity is a runtime property that inspection cannot verify.",
|
|
181
|
+
"symptom": "a write-then-publish path whose atomicity is asserted by code review only",
|
|
182
|
+
"fix": "add a forced-rollback integration test asserting entity rolled back AND no event dispatched"
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"key": "pd_context_propagation_sync_and_async",
|
|
186
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
187
|
+
"found_by": "project-decisions.md §14",
|
|
188
|
+
"tags": "dotnet,messaging",
|
|
189
|
+
"title": "Correlation/user/session context propagates across BOTH HTTP and message hops — async hops need the same restoration",
|
|
190
|
+
"body": "Three middlewares make the IDs flow through every hop: HTTP entry reads headers (correlation, JWT sub, session), sets Activity baggage, opens a logger scope, echoes the correlation ID back; the message-consuming side restores the same IDs from envelope headers; the outgoing side stamps baggage onto outgoing envelopes. The key insight is symmetry — without message-side restoration, traces and log scopes go dark the moment work crosses a queue."
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"key": "pd_otel_vendor_neutral",
|
|
194
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
195
|
+
"found_by": "project-decisions.md §14",
|
|
196
|
+
"tags": "dotnet",
|
|
197
|
+
"title": "Instrument with OpenTelemetry/OTLP, never vendor SDKs — and filter health probes out of traces",
|
|
198
|
+
"body": "The collection layer doesn't know where telemetry goes: OTLP is the standard every modern backend speaks, so swapping observability vendors is exporter config, not app-code change. The exporter activates only when the endpoint env var is set. Tracing filters out /health and /alive so liveness probes don't drown real request traces."
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"key": "pd_melogging_over_serilog",
|
|
202
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
203
|
+
"found_by": "project-decisions.md §15",
|
|
204
|
+
"tags": "dotnet",
|
|
205
|
+
"title": "Built-in Microsoft.Extensions.Logging piped through OTel — Serilog would add an adapter layer for wins .NET 8+ already has",
|
|
206
|
+
"body": "Serilog's historical advantages (structured templates, scopes, async sinks) are first-class in ME.Logging now, and AddOpenTelemetry() ingests it directly; Serilog would add an adapter and 3-4 packages. Its remaining value is the sink ecosystem, but with OTel the sink decision becomes the exporter decision — same backend flexibility, less coupling. Acknowledged exception: Seq's local filtering UI is genuinely nicer; not adopted because the marginal benefit doesn't justify a parallel pipeline."
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
"key": "pd_hybridcache_over_handrolled",
|
|
210
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
211
|
+
"found_by": "project-decisions.md §16",
|
|
212
|
+
"tags": "dotnet",
|
|
213
|
+
"title": "Two-tier caching uses HybridCache — hand-rolled L1/L2 grows three bugs you only find under load",
|
|
214
|
+
"body": "A hand-rolled memory-then-distributed-then-DB chain reliably develops: cache stampede (N concurrent misses invoke the factory N times, DDoSing the DB — async-safe per-key locking is hard), L1/L2 invalidation skew (one tier forgotten, stale until TTL), and serialization drift (ad-hoc serializer calls per site creating poison-pill entries). HybridCache solves all three by construction: single-flight factory execution, tag-based invalidation clearing both tiers, source-generated serialization."
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"key": "pd_hybridcache_no_backplane",
|
|
218
|
+
"type": "Claim", "scope": "org", "repo": "dotnet-microservices", "verified": true, "status": "live",
|
|
219
|
+
"found_by": "project-decisions.md §16 'What HybridCache doesn't do (yet)'",
|
|
220
|
+
"tags": "dotnet",
|
|
221
|
+
"title": "HybridCache 10.x has no cross-replica L1 backplane — a correctness bug the day you scale out",
|
|
222
|
+
"body": "When replica A invalidates a key, replica B keeps serving its stale in-process L1 entry until the L1 TTL — there is no invalidation backplane. Harmless single-replica; wrong answers multi-replica. Mitigations: drop the local-cache TTL to ~60s, or migrate to FusionCache (Redis pub/sub backplane). This is an explicit precondition for multi-replica deployment of any caching service.",
|
|
223
|
+
"symptom": "stale reads on some replicas after an invalidation, lasting up to the L1 TTL",
|
|
224
|
+
"fix": "shorten L1 TTL or adopt a backplane before scaling past one replica"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
"key": "pd_standard_resilience_handler",
|
|
228
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
229
|
+
"found_by": "project-decisions.md §17 (+ .claude/audits/2026-06-03-circuit-breaker verdict)",
|
|
230
|
+
"tags": "dotnet",
|
|
231
|
+
"title": "All HttpClients get the platform's standard resilience pipeline by default — no hand-rolled Polly stacks",
|
|
232
|
+
"body": "ConfigureHttpClientDefaults + AddStandardResilienceHandler gives every outbound HTTP call the curated Polly v8 pipeline — total timeout, exponential-backoff retry, circuit breaker, per-attempt timeout, and a rate limiter against thundering-herd on recovering services — in one line with zero drift across services. The circuit-breaker audit rejected a bespoke pipeline for exactly this reason: it re-implements capability the framework ships. Per-client tuning stays available; gRPC and bus traffic have their own retry machinery."
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"key": "pd_license_driven_test_lib_forks",
|
|
236
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
237
|
+
"found_by": "project-decisions.md §18 (commit 3fd6aee; + audits/2026-06-05-harmful-dotnet-packages)",
|
|
238
|
+
"tags": "dotnet,method",
|
|
239
|
+
"title": "When a test dependency changes license, a maintained MIT fork or API-equivalent alternative makes migration nearly free",
|
|
240
|
+
"body": "Moq added SponsorLink telemetry in 2023 → NSubstitute (also a cleaner API); FluentAssertions v8 went paid-commercial in 2024 → AwesomeAssertions, the community MIT fork of v7 with the identical API, making migration a one-commit package+namespace swap. The stack deliberately sidesteps the .NET OSS commercialization wave — and the audit that recommended moving TO Moq was rejected with receipts."
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
"key": "pd_testcontainers_real_providers",
|
|
244
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
245
|
+
"found_by": "project-decisions.md §18",
|
|
246
|
+
"tags": "dotnet",
|
|
247
|
+
"title": "Integration tests run against real DB/cache containers — InMemory and SQLite fakes hide provider-specific bugs",
|
|
248
|
+
"body": "Faking the database leaves whole bug classes uncaught: Postgres xmin concurrency tokens don't exist in SQLite, migrations can emit provider-specific SQL the in-memory provider doesn't model, real Redis behaves unlike fake Redis. Testcontainers boots real Postgres/SQL Server/Redis per test class with the real API hosted in-process. Slower than in-memory — and the only way to prove the seams actually work."
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
"key": "pd_macos_docker_host_testcontainers",
|
|
252
|
+
"type": "Rule", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
253
|
+
"found_by": "project-decisions.md §18",
|
|
254
|
+
"tags": "dotnet,method",
|
|
255
|
+
"title": "On macOS, Testcontainers needs DOCKER_HOST pointed at Docker Desktop's real socket",
|
|
256
|
+
"body": "Docker Desktop on macOS exposes its socket at ~/.docker/run/docker.sock, not the /var/run/docker.sock default Testcontainers probes; without DOCKER_HOST set (or the default-socket toggle enabled), integration tests fail fast with DockerUnavailableException while Docker is demonstrably running.",
|
|
257
|
+
"symptom": "DockerUnavailableException on macOS with Docker Desktop up",
|
|
258
|
+
"fix": "set DOCKER_HOST to unix://$HOME/.docker/run/docker.sock"
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
"key": "pd_central_package_management",
|
|
262
|
+
"type": "Rule", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
263
|
+
"found_by": "project-decisions.md §19",
|
|
264
|
+
"tags": "dotnet",
|
|
265
|
+
"title": "All package versions live in one central file; project files reference packages without versions",
|
|
266
|
+
"body": "Central Package Management puts every version in Directory.Packages.props, so 20 projects cannot drift onto different versions of the same package and a dependency bump is a one-file PR. One place to bump, zero drift.",
|
|
267
|
+
"symptom": "a Version attribute on a PackageReference in a csproj",
|
|
268
|
+
"fix": "move the version to the central props file"
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"key": "pd_zero_warning_build",
|
|
272
|
+
"type": "Rule", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
273
|
+
"found_by": "project-decisions.md §19",
|
|
274
|
+
"tags": "dotnet",
|
|
275
|
+
"title": "Every analyzer warning is a build error — layered analyzers each catch a distinct class, and banned APIs teach the fix",
|
|
276
|
+
"body": "TreatWarningsAsErrors + full analysis mode + style-enforced-in-build: zero warnings tolerated. Layers: built-in CA (async patterns), Meziantou (CancellationToken propagation, sync-over-async), Sonar (cognitive complexity, async void), Roslynator (style/perf), and BannedApiAnalyzers compile-rejecting Task.WaitAll/Parallel.For/Thread.Sleep with custom error messages pointing at the correct replacement — the ban teaches the fix instead of just blocking."
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"key": "pd_ci_grep_unanalyzable_hazards",
|
|
280
|
+
"type": "Rule", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
281
|
+
"found_by": "project-decisions.md §19",
|
|
282
|
+
"tags": "method",
|
|
283
|
+
"title": "When a hazard is structural and no analyzer can express it, enforce it with a CI grep step",
|
|
284
|
+
"body": "Static mutable collections are a real concurrency hazard the analyzer stack cannot detect (structural, not syntactic); the ban is enforced with a grep step in CI instead. An ugly-but-automated text check beats an unenforced convention — the enforcement ladder doesn't stop where analyzer expressiveness ends.",
|
|
285
|
+
"symptom": "a real hazard 'enforced' only by convention because no analyzer rule exists",
|
|
286
|
+
"fix": "encode it as a CI grep/audit step; ugly beats unenforced"
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
"key": "pd_dapr_considered_not_adopted",
|
|
290
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
291
|
+
"found_by": "project-decisions.md §22 (+ audits 2026-06-01 sidecar, 2026-06-12 Dapr Workflows)",
|
|
292
|
+
"tags": "dotnet,messaging",
|
|
293
|
+
"title": "Dapr rejected for a coherent single-stack system: sidecar hop on hot paths, stringly-typed invocation, an outbox coupled to Dapr's state model",
|
|
294
|
+
"body": "For a .NET-native stack Dapr would regress what exists: its outbox assumes Dapr-shaped state-store persistence (adopting it means adopting Dapr's data model), typed gRPC contracts become generic invoke-by-name calls, and every call pays a localhost sidecar hop. The five-SDKs-replaced pitch is a strawman when messaging+outbox is one framework, secrets are stock config, caching is HybridCache. Dapr fits polyglot teams, hard multi-cloud portability, or greenfields without an opinionated stack. Documented revisit triggers: non-.NET services, a hard portability requirement the transport swap doesn't cover, or wanting 3+ building blocks simultaneously."
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
"key": "pd_broker_swap_portability_oversold",
|
|
298
|
+
"type": "Claim", "scope": "org", "repo": "dotnet-microservices", "verified": true, "status": "live",
|
|
299
|
+
"found_by": "project-decisions.md §22",
|
|
300
|
+
"tags": "messaging",
|
|
301
|
+
"title": "'Swap brokers via config' is oversold — broker semantics don't survive the swap",
|
|
302
|
+
"body": "Abstraction layers promising broker portability via one config line deliver it only for trivial fire-and-forget publishes: sessions, FIFO, dead-lettering, and unique-subscription-name constraints have no clean mapping across brokers. Real portability planning names the concrete target swap and verifies the semantic features in use actually map — rather than trusting the abstraction's marketing."
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"key": "pd_choreography_with_flip_trigger",
|
|
306
|
+
"type": "Decision", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
307
|
+
"found_by": "project-decisions.md §22",
|
|
308
|
+
"tags": "messaging,method",
|
|
309
|
+
"title": "The saga stays choreography — with a concrete written trigger for when durable orchestration would earn its keep",
|
|
310
|
+
"body": "At 4 shallow, sub-second, event-shaped steps, choreography's known cost (flow scattered across handlers, unreadable top-to-bottom) is cheap, and the trade buys loose coupling and no orchestrator to operate. The documented flip trigger: if compensation work grows timeouts, first-class retries, human-approval gates, multi-day waits, or 'where is order X stuck?' becomes unanswerable from event logs — run a three-way bake-off (Temporal vs Durable Functions vs Dapr Workflow). Writing the concrete re-evaluation trigger into the decision beats both 'never' and vague 'maybe later'."
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
"key": "pd_db_primitives_before_distributed_locks",
|
|
314
|
+
"type": "Rule", "scope": "org", "repo": "dotnet-microservices", "verified": true,
|
|
315
|
+
"found_by": "project-decisions.md §22",
|
|
316
|
+
"tags": "method,data-quality",
|
|
317
|
+
"title": "Reach for database primitives before distributed locks — the DB almost always solves it first",
|
|
318
|
+
"body": "The mapping: concurrent row updates → optimistic concurrency tokens; duplicate creation → unique constraints; 'reserve X for 10 min' → a reservation row with TTL/status and a unique constraint on the scarce dimension; idempotent event processing → idempotency-key/envelope-ID dedup; serializing a critical section in one DB → SELECT FOR UPDATE or advisory locks; atomic write-plus-publish → transactional outbox. A walk of all five services found zero legitimate distributed-lock needs. The genuine future case is singleton scheduled jobs on multi-replica services — then a lock library over the DB you already run, never a new runtime or sidecar.",
|
|
319
|
+
"symptom": "a distributed lock (or lock service) proposed for a problem in the mapping above",
|
|
320
|
+
"fix": "use the matching DB primitive; locks only for true singleton-job coordination"
|
|
321
|
+
}
|
|
322
|
+
],
|
|
323
|
+
"edges": [
|
|
324
|
+
{"src": "pd_nonhandler_outbox_idbcontextoutbox", "rel": "SUPERSEDES", "dst": "seed:dotnet-defects:nc_outbox_atomicity"},
|
|
325
|
+
{"src": "pd_rollback_test_write_then_publish", "rel": "CATCHES", "dst": "pd_nonhandler_outbox_idbcontextoutbox"},
|
|
326
|
+
{"src": "pd_choreography_with_flip_trigger", "rel": "DEFINED_IN", "dst": "pd_dapr_considered_not_adopted"}
|
|
327
|
+
]
|
|
328
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Seed for the OKL — real, dated, receipted lessons from the .NET platform (.NET 10 microservices: Aspire, RabbitMQ/Wolverine, gRPC, EF Core). the .NET platform is the origin repo of the encoding loop. Portable/world-fact lessons are org-scoped; .NET-framework-specific quirks are scoped repo:dotnet-microservices so they don't fire in a Python or geospatial repo's okl check.",
|
|
3
|
+
"nodes": [
|
|
4
|
+
{
|
|
5
|
+
"key": "nc_idor",
|
|
6
|
+
"type": "Defect",
|
|
7
|
+
"scope": "org",
|
|
8
|
+
"repo": "dotnet-microservices",
|
|
9
|
+
"found_by": "security review; original GET /orders/{id} IDOR survived the codebase lifetime",
|
|
10
|
+
"verified": true,
|
|
11
|
+
"title": "Missing ownership scope check is an IDOR (CWE-639) and slips through tests-by-omission",
|
|
12
|
+
"body": "A resource endpoint that doesn't scope by the caller's identity leaks other users' data. Portable fix: push the ownership predicate INTO the query (read handlers) or check on the tracked entity (write handlers), return 404 not 403 (403 leaks existence). Require an integration test asserting user X cannot read user Y's resource for every scoped-entity endpoint.",
|
|
13
|
+
"symptom": "an endpoint fetches an entity by id with no owner/tenant predicate",
|
|
14
|
+
"fix": "add the caller's owner id to the WHERE clause; return 404 (not 403) on no match",
|
|
15
|
+
"tags": "security,dotnet"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"key": "nc_jwt_clockskew",
|
|
19
|
+
"type": "Defect",
|
|
20
|
+
"scope": "repo",
|
|
21
|
+
"repo": "dotnet-microservices",
|
|
22
|
+
"found_by": "JWT config audit",
|
|
23
|
+
"verified": true,
|
|
24
|
+
"title": "Default JWT ClockSkew (5 min) doubles the effective lifetime of short-lived tokens",
|
|
25
|
+
"body": ".NET's TokenValidationParameters defaults ClockSkew to 5 minutes; on a realm with 5-minute access tokens that doubles every token's effective lifetime. Set ClockSkew = 30s explicitly. Stack-specific (.NET), scoped repo.",
|
|
26
|
+
"symptom": "TokenValidationParameters without an explicit ClockSkew",
|
|
27
|
+
"fix": "set ClockSkew to ~30s; the 5-min default doubles short-token lifetime",
|
|
28
|
+
"tags": "security,dotnet"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"key": "nc_price_tamper",
|
|
32
|
+
"type": "Defect",
|
|
33
|
+
"scope": "org",
|
|
34
|
+
"repo": "dotnet-microservices",
|
|
35
|
+
"found_by": "security review",
|
|
36
|
+
"verified": true,
|
|
37
|
+
"title": "Server-controlled fields trusted from the client body = tampering vulnerability",
|
|
38
|
+
"body": "A [FromBody] DTO carrying Price/BuyerId/Status/IsAdmin lets a client submit Price=0.01 for a $999 product. Portable rule: money, authorization identifiers, state-machine columns, and security flags are computed server-side from the authoritative source (catalog service, JWT sub, DB) — the request body is untrusted input.",
|
|
39
|
+
"symptom": "a [FromBody] DTO carries Price/Amount/Status/IsAdmin",
|
|
40
|
+
"fix": "drop those fields from the DTO; compute them server-side from the catalog/service",
|
|
41
|
+
"tags": "security"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"key": "nc_outbox_atomicity",
|
|
45
|
+
"type": "Defect",
|
|
46
|
+
"scope": "repo",
|
|
47
|
+
"repo": "dotnet-microservices",
|
|
48
|
+
"found_by": "docs/war-story-wolverine6-outbox-atomicity.md",
|
|
49
|
+
"verified": true,
|
|
50
|
+
"title": "Constructor-injected IEventPublisher publishes inline under Wolverine 6, breaking outbox atomicity",
|
|
51
|
+
"body": "Only the IMessageContext Wolverine injects as a HandleAsync parameter (or IDbContextOutbox in non-handler code) is enlisted in the outbox transaction. A constructor-injected IMessageBus/IEventPublisher publishes before commit — events dispatched for entity writes that may roll back. Stack-specific (Wolverine), scoped repo.",
|
|
52
|
+
"tags": "dotnet"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"key": "nc_handler_di",
|
|
56
|
+
"type": "Defect",
|
|
57
|
+
"scope": "repo",
|
|
58
|
+
"repo": "dotnet-microservices",
|
|
59
|
+
"found_by": "OrderReadProjectionTests failed in CI after the repository-wrapper refactor",
|
|
60
|
+
"verified": true,
|
|
61
|
+
"title": "Wolverine handler discovery is NOT DI registration — GetRequiredService<Handler> throws unless AddScoped'd",
|
|
62
|
+
"body": "opts.Discovery.IncludeAssembly builds Wolverine's internal message->handler map; Wolverine constructs handlers itself and never asks IServiceCollection. Any handler resolved by GetRequiredService<T>() in tests needs an explicit AddScoped<T>(). Stack-specific (Wolverine), scoped repo.",
|
|
63
|
+
"tags": "dotnet"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"key": "nc_topology_race",
|
|
67
|
+
"type": "Defect",
|
|
68
|
+
"scope": "repo",
|
|
69
|
+
"repo": "dotnet-microservices",
|
|
70
|
+
"found_by": "issue #168",
|
|
71
|
+
"verified": true,
|
|
72
|
+
"title": "Fanout exchanges silently discard unroutable messages before a consumer's first boot",
|
|
73
|
+
"body": "AutoProvision declares topology lazily per service; an event published before a consumer's queue+binding exists is dropped while the outbox marks it delivered. Each publisher must declare its own exchange AND its consumers' queues+bindings; names from shared constants, never inline literals (a typo is auto-provisioned as an empty object and the consumer starves silently). Stack-specific (RabbitMQ/Wolverine), scoped repo.",
|
|
74
|
+
"tags": "dotnet"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"key": "nc_dead_metric",
|
|
78
|
+
"type": "Defect",
|
|
79
|
+
"scope": "org",
|
|
80
|
+
"repo": "dotnet-microservices",
|
|
81
|
+
"found_by": "review; deleted in #171",
|
|
82
|
+
"verified": true,
|
|
83
|
+
"title": "A declared-but-never-incremented metric is a working-looking alarm that never fires",
|
|
84
|
+
"body": "A AppMetrics class was registered but never injected; all five counters were dead while docs presented one as THE dead-letter-queue alarm. An operator wires an alert to it and it never fires — worse than no counter. Portable: every counter needs at least one increment call site, every metrics holder must be injected somewhere.",
|
|
85
|
+
"tags": "method"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"key": "nc_speculative_interface",
|
|
89
|
+
"type": "Rule",
|
|
90
|
+
"scope": "org",
|
|
91
|
+
"repo": "dotnet-microservices",
|
|
92
|
+
"found_by": "the simplicity refactor (five repositories deleted)",
|
|
93
|
+
"verified": true,
|
|
94
|
+
"title": "Interfaces earn their keep through consumer substitution, not 'future swap'",
|
|
95
|
+
"body": "A port/adapter interface is justified only if (a) a test substitutes it today, (b) 2+ concrete impls are registered today, or (c) a second impl is on a concrete near-term roadmap. Otherwise it's speculative coupling — delete it, take the concrete class. A factory returning one impl for every input is the same coupling. Portable design rule.",
|
|
96
|
+
"tags": "method"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"key": "nc_arch_reviewer",
|
|
100
|
+
"type": "Defect",
|
|
101
|
+
"scope": "org",
|
|
102
|
+
"repo": "dotnet-microservices",
|
|
103
|
+
"found_by": "asking whether it ran",
|
|
104
|
+
"verified": true,
|
|
105
|
+
"title": "A surface nobody runs is documentation, not enforcement",
|
|
106
|
+
"body": "The architecture-reviewer agent was described as a live Tier-2 enforcement surface in six documents while being invoked by nothing, ever. The single most transferable lesson: a rule that is only written down drifts; wire it to something that actually runs. (Also recorded independently in the geospatial repo as error #10.)",
|
|
107
|
+
"tags": "method"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"key": "g_tombstones_nc",
|
|
111
|
+
"type": "Gate",
|
|
112
|
+
"scope": "org",
|
|
113
|
+
"found_by": "dotnet-microservices .claude/scripts/check-tombstones.sh",
|
|
114
|
+
"verified": true,
|
|
115
|
+
"title": "Tombstone audit — group-scoped exemptions, fail on invalid regex, scan untracked",
|
|
116
|
+
"body": "The compiler catches stale identifiers in code; nothing catches them in docs/comments/config. A memory-based sweep left 15+ docs teaching Azure Service Bus as current after the RabbitMQ swap. Gate: git grep every tombstoned identifier; exemptions are scoped to ONE group (a file allowlisted for a past removal is still audited against future tombstones); an invalid regex must FAIL the audit, not silently disable itself.",
|
|
117
|
+
"tags": "method",
|
|
118
|
+
"repo": "dotnet-microservices"
|
|
119
|
+
}
|
|
120
|
+
],
|
|
121
|
+
"edges": [
|
|
122
|
+
{
|
|
123
|
+
"src": "g_tombstones_nc",
|
|
124
|
+
"rel": "CATCHES",
|
|
125
|
+
"dst": "nc_speculative_interface"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"src": "nc_arch_reviewer",
|
|
129
|
+
"rel": "RECURS_IN",
|
|
130
|
+
"dst": "dotnet-microservices"
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
}
|