chore(devex): adopt the facade wiring-couplings doctrine - #71486
Conversation
architecture.md said facade/api.py is the only importable file, while tach.toml and the isolation rules already expose the whole facade package with capability submodules — the doc lost that argument long ago. Catch it up and close the gap it left: nothing constrained WHAT may cross, which is how facades started handing out ORM models, ad-hoc classes, and even a test mixin. Shopify's Packwerk public folders rotted into a catch-all drawer for exactly this reason: location granted publicness without an API shape. The doctrine: data crosses as frozen dataclasses; behavior crosses only as an implementation of a core-owned base (QueryRunner, MaxTool, Temporal defns, shared_task) defined in a wiring location that stays in the contract-check inputs; registration points validate with issubclass, since no static Python tool can check what an object is — tach and import-linter only see the import graph. Django models never cross (a model cannot be narrowed); two class-identity registries are named carve-outs. Anything else behavioral loses the contract-check skip.
Per-file input widenings bet on a hand-maintained list being complete — fail-dangerous, and the awkward lists were multiplying. Contract-check inputs are binary now: doctrine-clean products narrow to facade, presentation, routes, and their wiring locations; everything else drops the turbo.json override entirely and inherits the watch-everything default, so any change re-runs the full suite until the product earns the skip back. Moves that make products clean: MetricsQueryRunner into backend/hogql_queries/, SearchErrorTrackingIssuesTool and HogQLQueryFixerTool into backend/max_tools.py — each already wears a core-owned base, only the file lived outside the wiring location. Pure error types move to facade/contracts.py (streamlit_apps app-runtime errors, customer_analytics InvalidCustomPropertyOptions). demo is demoted outright: its facade hands out MatrixManager and the matrix classes with no core-owned interface, driven from signup and the demo management commands. No sound skip exists for that shape. tasks, warehouse_sources, data_modeling, data_warehouse and endpoints keep their script but watch everything — their facades still hand out classes from non-wiring modules (several with live consumers, two pinned by migration files), so narrowing waits until those move.
hogli product:lint now reads what a facade actually hands out instead of trusting where files sit. Two checks: a class a facade module re-exports from outside the wiring locations blocks narrowing — a hard error when the product is narrowed, a warning while it watches everything — and a wiring location that exists but isn't in the contract-check inputs is an error, mirroring the routes rule. Detection is keyed on import statements, not __all__: any module-level binding in a facade module is importable under tach's facade exposure, advertised or not. Pure re-export modules count everything they import; function-bearing modules count __all__ plus the Foo-as-Foo self-alias idiom (the shape ruff F401 deliberately ignores); PEP 562 lazy maps are read as the import map they are. Coverage stays prefix-based — wiring locations are directory conventions, so no glob engine. The gate deliberately targets narrowing, not the script: script plus broad inputs is inert (everything re-runs), so it stays a valid interim state, and the missing-narrowing nag is suppressed while a leak blocks the narrowing it would demand. Two class-identity registry carve-outs (team extension, file-system unfiled) are allowed but their defining modules must stay watched.
🤖 CI report
|
| Root | Eager (shipped) | Δ vs base | Budget |
|---|---|---|---|
entry (logged-out pages, app bootstrap)src/index.tsx |
1.23 MiB · 22 files | no change | ███░░░░░░░ 28.6% of 4.29 MiB |
authenticated shell (every logged-in page)src/scenes/AuthenticatedShell.tsx |
8.15 MiB · 2,988 files | no change | █████████░ 88.1% of 9.25 MiB |
🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
Largest files eagerly shipped from src/index.tsx
| Size | File |
|---|---|
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 24.6 KiB | ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js |
| 6.3 KiB | ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js |
| 4.5 KiB | ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js |
| 3.9 KiB | ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js |
| 1.4 KiB | ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js |
| 1.3 KiB | src/RootErrorBoundary.tsx |
| 912 B | ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js |
| 789 B | src/scenes/ChunkLoadErrorBoundary.tsx |
| 762 B | src/index.tsx |
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
| Size | File |
|---|---|
| 281.3 KiB | ../node_modules/.pnpm/posthog-js@1.404.0/node_modules/posthog-js/dist/rrweb.js |
| 267.7 KiB | ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js |
| 235.5 KiB | src/taxonomy/core-filter-definitions-by-group.json |
| 223.2 KiB | ../node_modules/.pnpm/posthog-js@1.404.0/node_modules/posthog-js/dist/module.js |
| 164.0 KiB | src/queries/validators.js |
| 154.3 KiB | ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js |
| 126.8 KiB | ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js |
| 105.8 KiB | src/lib/api.ts |
| 93.3 KiB | ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js |
| 93.2 KiB | ../packages/quill/packages/quill/dist/index.js |
Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479
✅ Dist folder size — 🔺 +950 B (+0.0%)
Total size of the built frontend/dist folder (all assets), compared against the base branch.
Total: 1336.73 MiB · 🔺 +950 B (+0.0%)
⚠️ Backend coverage — 78.0% of changed backend lines covered — 37 uncovered
🧪 Backend test coverage
Patch coverage — changed backend lines (products + core): ████████████████░░░░ 78.0% (134 / 171)
| File | Patch | Uncovered changed lines |
|---|---|---|
products/error_tracking/backend/facade/tools.py |
0.0% | 10 |
products/streamlit_apps/backend/tasks/tasks.py |
0.0% | 51–52 |
products/warehouse_sources/backend/temporal/data_imports/sources/tests/test_ci_core_coupled_sources.py |
60.0% | 141–144 |
products/error_tracking/backend/max_tools.py |
79.0% | 279, 312–315, 322–327, 429–430, 452–454, 456–465, 467, 479–481 |
🤖 Agents: add a test covering the lines above, or note why under "How did you test this code?". Machine-readable gap list: the patch-coverage artifact on this run (gh run download 29594072949 -n patch-coverage), or the coverage-data block at the end of this comment.
Per-product line coverage (touched products)
| Product | Coverage | Lines |
|---|---|---|
platform_features |
██░░░░░░░░░░░░░░░░░░ 12.1% |
7 / 58 |
batch_exports |
████████░░░░░░░░░░░░ 39.7% |
8,416 / 21,225 |
demo |
███████████░░░░░░░░░ 56.2% |
1,497 / 2,663 |
warehouse_sources_queue |
████████████░░░░░░░░ 59.2% |
148 / 250 |
tasks |
█████████████░░░░░░░ 67.1% |
25,922 / 38,649 |
data_tools |
██████████████░░░░░░ 70.0% |
63 / 90 |
ai_gateway |
███████████████░░░░░ 75.0% |
9 / 12 |
signals |
████████████████░░░░ 79.2% |
19,224 / 24,265 |
data_modeling |
████████████████░░░░ 80.0% |
4,834 / 6,045 |
cdp |
████████████████░░░░ 80.7% |
3,118 / 3,864 |
wizard |
████████████████░░░░ 82.5% |
772 / 936 |
notebooks |
█████████████████░░░ 84.8% |
6,903 / 8,141 |
cohorts |
█████████████████░░░ 86.2% |
4,065 / 4,717 |
agent_platform |
█████████████████░░░ 86.4% |
3,807 / 4,405 |
actions |
█████████████████░░░ 86.6% |
717 / 828 |
product_tours |
█████████████████░░░ 87.5% |
1,266 / 1,447 |
exports |
██████████████████░░ 88.4% |
6,933 / 7,842 |
visual_review |
██████████████████░░ 88.5% |
5,565 / 6,287 |
business_knowledge |
██████████████████░░ 88.5% |
4,400 / 4,969 |
conversations |
██████████████████░░ 89.0% |
16,183 / 18,186 |
mcp_analytics |
██████████████████░░ 89.2% |
2,514 / 2,819 |
engineering_analytics |
██████████████████░░ 89.3% |
5,407 / 6,053 |
dashboards |
██████████████████░░ 89.4% |
5,847 / 6,540 |
error_tracking |
██████████████████░░ 89.9% |
9,758 / 10,854 |
alerts |
██████████████████░░ 89.9% |
4,045 / 4,499 |
early_access_features |
██████████████████░░ 90.1% |
1,031 / 1,144 |
streamlit_apps |
██████████████████░░ 90.4% |
2,501 / 2,767 |
slack_app |
██████████████████░░ 90.6% |
9,018 / 9,955 |
links |
██████████████████░░ 90.6% |
183 / 202 |
marketing_analytics |
██████████████████░░ 90.8% |
11,514 / 12,684 |
stamphog |
██████████████████░░ 91.0% |
3,993 / 4,387 |
product_analytics |
██████████████████░░ 91.3% |
5,757 / 6,304 |
mcp_store |
██████████████████░░ 91.8% |
3,685 / 4,012 |
managed_migrations |
██████████████████░░ 91.9% |
908 / 988 |
data_warehouse |
██████████████████░░ 92.4% |
18,626 / 20,158 |
notifications |
███████████████████░ 92.7% |
1,031 / 1,112 |
web_analytics |
███████████████████░ 92.7% |
13,535 / 14,597 |
workflows |
███████████████████░ 92.8% |
5,482 / 5,909 |
ai_observability |
███████████████████░ 92.8% |
14,916 / 16,077 |
surveys |
███████████████████░ 93.0% |
5,724 / 6,157 |
posthog_ai |
███████████████████░ 93.2% |
1,322 / 1,418 |
approvals |
███████████████████░ 93.3% |
3,395 / 3,640 |
reminders |
███████████████████░ 93.4% |
468 / 501 |
tracing |
███████████████████░ 93.4% |
2,545 / 2,724 |
legal_documents |
███████████████████░ 94.1% |
1,568 / 1,667 |
endpoints |
███████████████████░ 94.1% |
8,606 / 9,143 |
revenue_analytics |
███████████████████░ 94.5% |
3,598 / 3,809 |
skills |
███████████████████░ 94.5% |
2,881 / 3,049 |
messaging |
███████████████████░ 94.5% |
2,530 / 2,677 |
review_hog |
███████████████████░ 94.6% |
6,789 / 7,174 |
logs |
███████████████████░ 95.2% |
9,439 / 9,911 |
experiments |
███████████████████░ 95.7% |
24,417 / 25,527 |
replay_vision |
███████████████████░ 95.8% |
13,776 / 14,383 |
growth |
███████████████████░ 95.8% |
2,837 / 2,960 |
annotations |
███████████████████░ 96.2% |
732 / 761 |
feature_flags |
███████████████████░ 96.3% |
16,172 / 16,794 |
warehouse_sources |
███████████████████░ 96.4% |
283,692 / 294,224 |
user_interviews |
███████████████████░ 96.4% |
2,242 / 2,325 |
access_control |
███████████████████░ 96.8% |
849 / 877 |
customer_analytics |
███████████████████░ 97.2% |
7,482 / 7,700 |
data_catalog |
███████████████████░ 97.4% |
2,304 / 2,365 |
analytics_platform |
████████████████████ 98.0% |
2,102 / 2,145 |
metrics |
████████████████████ 98.2% |
2,404 / 2,448 |
pulse |
████████████████████ 98.4% |
2,017 / 2,049 |
live_debugger |
████████████████████ 99.2% |
613 / 618 |
field_notes |
████████████████████ 99.4% |
158 / 159 |
Report-only. Patch coverage = changed backend lines covered vs origin/master. Sorted lowest first.
Known gaps: lines covered only by Temporal tests show as uncovered; core line numbers may drift if master changed the same file.
👀 Auto-assigned reviewersThese soft owners were skipped because their changes are minor, or the reviewer list was getting long. Nothing blocks merge, so self-assign if you'd like a look:
Soft owners come from each directory's |
There was a problem hiding this comment.
Pull request overview
Updates PostHog’s product-isolation “contract-check” doctrine to make turbo’s Django-suite skipping sound when facades re-export behavior, by codifying where behavior is allowed to live (“wiring locations”) and enforcing it via hogli product:lint, with several products adjusted to either watch the right wiring paths or fall back to watch-everything.
Changes:
- Add “Wiring couplings” doctrine and align product-isolation docs accordingly.
- Add AST-based detection + lint gating for facade class re-exports outside wiring locations, plus coverage checks ensuring existing wiring locations are watched by narrowed
turbo.json. - Move/reshape a few product entry points (query runners, Max tools, error types) and adjust
turbo.jsoninputs (or remove narrowing) to match the doctrine.
Reviewed changes
Copilot reviewed 38 out of 41 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/hogli-commands/hogli_commands/tests/test_checks.py | Adds tests for facade class-leak detection and garage coverage gating. |
| tools/hogli-commands/hogli_commands/product/isolation.py | Implements facade re-export detection, garage/carve-out coverage checks, and updated narrowing logic. |
| tools/hogli-commands/hogli_commands/product/checks.py | Surfaces and enforces wiring-coupling violations in product lint output and script gating. |
| tools/hogli-commands/hogli_commands/product/ast_helpers.py | Adds AST helpers for __all__, lazy re-export maps, import-from harvesting, and top-level function detection. |
| products/warehouse_sources/turbo.json | Removes narrowing override (watch everything). |
| products/visual_review/turbo.json | Adds backend/tasks/** to contract-check inputs. |
| products/tasks/turbo.json | Removes narrowing override (watch everything). |
| products/streamlit_apps/turbo.json | Adds backend/tasks/** to contract-check inputs. |
| products/streamlit_apps/backend/tasks/tasks.py | Switches concurrency error import to facade contracts. |
| products/streamlit_apps/backend/logic/app_runtime.py | Moves error types out to facade contracts. |
| products/streamlit_apps/backend/facade/contracts.py | Adds AppRuntimeError and AppRuntimeConcurrencyError to contract surface. |
| products/streamlit_apps/backend/facade/api.py | Imports error types from facade contracts rather than logic. |
| products/metrics/turbo.json | Adds wiring locations (hogql_queries/**, tasks/**) to inputs. |
| products/metrics/backend/tests/test_metrics_query_runner.py | Updates import to new hogql_queries location. |
| products/metrics/backend/hogql_queries/metrics_query_runner.py | New QueryRunner implementation living under wiring location. |
| products/metrics/backend/facade/queries.py | Re-exports runner from wiring location. |
| products/legal_documents/turbo.json | Adds backend/tasks/** to contract-check inputs. |
| products/error_tracking/turbo.json | Adds wiring locations (hogql_queries, max_tools, temporal, tasks) to inputs. |
| products/error_tracking/backend/test/test_search_issues.py | Updates tool import path to wiring location (max_tools.py). |
| products/error_tracking/backend/max_tools.py | Moves Search tool implementation into wiring location module. |
| products/error_tracking/backend/logic/tools/test/init.py | Maintains/adjusts test package structure after tool move. |
| products/error_tracking/backend/logic/tools/search_issues.py | Removes old tool implementation module. |
| products/error_tracking/backend/logic/tools/init.py | Removes old re-export barrel. |
| products/error_tracking/backend/facade/tools.py | Updates facade re-export to new wiring location module. |
| products/engineering_analytics/turbo.json | Adds backend/tasks/** to contract-check inputs. |
| products/endpoints/turbo.json | Removes narrowing override (watch everything). |
| products/demo/turbo.json | Removes narrowing override (watch everything). |
| products/demo/package.json | Drops backend:contract-check script (no longer isolated). |
| products/demo/backend/facade/contracts.py | Updates rationale to state demo is not isolated due to behavioral facade surface. |
| products/data_warehouse/turbo.json | Removes narrowing override (watch everything). |
| products/data_warehouse/backend/tests/api/test_fix_hogql.py | Updates imports/mocks to new wiring location module. |
| products/data_warehouse/backend/test/test_hogql_fixer_ai.py | Updates imports to new wiring location module. |
| products/data_warehouse/backend/max_tools.py | Introduces HogQL fixer tool implementation in wiring location module. |
| products/data_warehouse/backend/facade/api.py | Updates lazy facade map entry to point to max_tools. |
| products/data_modeling/turbo.json | Removes narrowing override (watch everything). |
| products/customer_analytics/turbo.json | Adds wiring locations + carve-out module to contract-check inputs. |
| products/customer_analytics/backend/logic/custom_property_definitions.py | Moves exception type import to facade contracts. |
| products/customer_analytics/backend/facade/contracts.py | Adds InvalidCustomPropertyOptions to contract surface. |
| products/customer_analytics/backend/facade/api.py | Re-exports exception via facade contracts (no longer from logic). |
| products/architecture.md | Adds “Wiring couplings” doctrine; clarifies facade package surface. |
| .claude/rules/product-isolation.md | Updates agent guidance to match wiring-couplings doctrine. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def _format_date(self, date_value) -> str: | ||
| """Format a date value for display in UTC.""" | ||
| if not date_value: | ||
| return "" | ||
| from datetime import datetime | ||
|
|
There was a problem hiding this comment.
Fixed — merged into the module-level datetime import. The inline import traveled along with the file move.
Anchor garage coverage on the trailing slash so near-miss inputs (backend/tasks.py, backend/tasks_extra/**) can't count as watching backend/tasks/ — Codex and Copilot found this independently; near-miss rows in the parametrized test pin it. Watch backend/tasks/** in the scaffold template so a freshly bootstrapped product passes the garage check. This is what broke the repo-checks CI job: CI bootstraps spline_reticulator before linting, which local lint runs never did. The narrowing nag now also names wiring locations so following its advice doesn't trip the same check. Treat a missing turbo.json as the broad watch-everything default in the warehouse_sources vendor guard test instead of crashing on the file the narrow-or-nothing commit deleted; the vendor enumeration resumes if narrowing ever returns. Move the datetime import to module level in error_tracking max_tools — the inline import traveled along with the file move.
Follow-up to the slash-anchoring fix: file garages had the symmetric hole. backend/tasks.py.bak or backend/max_tools.py_extra/** would startswith-match the real file, so unwatched_garages() certified the wiring location as watched while changes to the actual file never re-ran contract-check. Directory locations keep the slash-anchored prefix; single-file locations now need an exact input. Applied in the shared coverage helper, so permanent modules and carve-out files get the same exactness.
There was a problem hiding this comment.
💡 Codex Review
posthog/products/data_warehouse/backend/max_tools.py
Lines 183 to 186 in e42e2b4
When a client supplies contextual_tools: {"fix_hogql_query": ...} to the conversation API, the registry imports every products.*.backend.max_tools module and this MaxTool subclass is now registered globally; unlike the FixHogQLViewSet path, it still inherits the default empty get_required_resource_access(). That exposes the schema-serializing, billable fixer outside the endpoint's scope_object = "INTERNAL" gate, so add an appropriate access check or keep this class out of the auto-imported max_tools registry.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "tasks": { | ||
| "backend:contract-check": { | ||
| "inputs": ["backend/facade/**", "backend/presentation/**", "backend/routes.py"], | ||
| "inputs": ["backend/facade/**", "backend/presentation/**", "backend/routes.py", "backend/tasks/**"], |
There was a problem hiding this comment.
When only products/engineering_analytics/backend/logic/job_logs/temporal.py or schedule.py changes, this narrowed input set will not mark backend:contract-check as affected, so turbo-discover can skip Django even though core registers JOB_LOGS_WORKFLOWS, JOB_LOGS_ACTIVITIES, and the schedule through backend/facade/temporal.py. Either move these definitions under a watched wiring location such as backend/temporal/ or include the current backend/logic/job_logs/** implementation path here.
Useful? React with 👍 / 👎.
…wiring-doctrine Conflict: products/warehouse_sources/turbo.json — deleted here (narrow-or-nothing: the facade still re-exports 21 non-contract classes, so the product can't hold a narrowed override), modified on master by #71908 (input negations to cut CI fan-out). Resolved by keeping the deletion. The IsolationChainCheck gate this branch adds rejects any narrowing while those leaks exist, and the narrowed inputs from #71908 leave backend/types.py and the facade/testing.py mixin unwatched — real coverage holes. warehouse_sources earns narrowing back by moving its facade-crossing types to contracts.py; the #71908 audit maps most of that surface already.
The hosted-platform merge gave stamphog backend/tasks/ and backend/temporal/ garages, but its turbo.json narrowing predates them — the new unwatched-garages gate correctly flags that a change to core-driven implementations there would skip the Django suite.
|
Merging master hit a conflict with #71908, which re-narrowed Also added stamphog's new |
The isolating-product-facade-contracts skill told authors to narrow backend:contract-check inputs to only backend/facade/** and backend/presentation/**. Since the wiring-couplings doctrine (#71486), core-dispatched implementations (query runners, Max tools, Temporal workflows, celery tasks) and sanctioned model-registration modules must stay in those inputs so a change to them still re-runs the full suite. Following the skill verbatim preserved an unsound CI skip. Update step 6.4 and the done criteria to require the wiring locations (and backend/routes.py) alongside facade/presentation, and point at the error_tracking and visual_review turbo.json examples. Generated-By: PostHog Code Task-Id: f15e5796-88fe-432e-8301-938605c272ba
Problem
turbo-discoverskips the full Django suite for products declaringbackend:contract-check. That skip is only sound if nothing inside the product can change what the outside world observes without a watched file changing. Several facades hand out behavioral classes defined in unwatched internals. Live case:MetricsQueryRunnertravels out through the metrics facade, is defined in an unwatched module, and core's query dispatch instantiates it, so an implementation change could sail through with the suite skipped.#71127 tried to fix this with an AST detector keyed on
__all__plus per-file turbo.json widenings. Review rounds kept finding holes (lazy facades, aliases, duplicate names, glob semantics), and dozens of facade modules declare no__all__at all, so the inspection list could not be trusted. The deeper gap: architecture.md never said what may cross a facade, which is how facades accumulated ORM models, ad hoc classes, and a test mixin. Shopify's Packwerk public folders famously rotted the same way once location granted publicness without an API shape.Changes
Doctrine first, in a new "Wiring couplings" section of
products/architecture.md(plus the isolation rule):QueryRunner,MaxTool, Temporal defns,@shared_task) defined in a wiring location (backend/hogql_queries/,backend/max_tools.py,backend/temporal/,backend/tasks/) that stays in the contract-check inputsissubclass- no static Python tool can check what an object is, only where imports point (MaxToolalready does this and is the reference)State changes make the doctrine true. Contract-check inputs are now binary: clean products narrow to facade + presentation + routes + wiring locations, and products whose facades still hand out unsanctioned classes drop their turbo.json override entirely and watch everything (fail-safe) until the classes move - no more hand-maintained per-file glob lists. Three files move into wiring locations, six pure error types move into facade contracts, and demo loses
backend:contract-checkoutright: its matrix classes implement no core interface and are driven straight from signup.Lint closes the loop:
hogli product:lintnow flags a class a facade re-exports from outside the wiring locations, and a wiring location that exists but is not watched. Detection is keyed on import statements rather than__all__(including PEP 562 lazy maps and theFoo as Fooidiom that ruff F401 deliberately ignores). Leaks gate narrowing, not the script, so watch-everything stays a valid interim state and the five interim products (tasks, warehouse_sources, data_modeling, data_warehouse, endpoints) earn narrowing back by relocating their types.How did you test this code?
All automated, run by the agent:
hogli product:lint --all: all 67 products passhogli test tools/hogli-commands: 1206 passed (one pre-existing environment-sensitive devbox test fails identically without these changes)tach check,lint-imports, ruff,hogli ci:preflight --fixall cleanNew tests in hogli-commands cover the detection shapes (eager, lazy, self-alias, TYPE_CHECKING, third-party, one-hop package re-export), wiring-location coverage including the flat-file form, the narrowed-vs-broad gating split, and the carve-outs. The regression they catch that nothing else did: a detector miss silently re-arms an unsound suite skip.
Automatic notifications
Docs update
products/architecture.mdand.claude/rules/product-isolation.mdare updated in this PR.🤖 Agent context
Autonomy: Human-driven (agent-assisted)
Written by Claude (Claude Code), human-directed throughout. This replaces #71127 after we concluded its
__all__-keyed detector could not be made trustworthy (most facade modules have no__all__, so the gate certified only the advertised subset of the surface). Skills invoked:/simplify(four-angle review; its findings were applied, including a copy-paste drift bug between coverage helpers) plus the repo test-writing conventions for the new tests.Decisions worth knowing at review: leaks gate narrowing rather than eligibility, because changing eligibility would force the five interim products to drop their script instead of watching everything; DRF viewsets are deliberately not on the approved interface list (they are the presentation channel with its own rules and never pass through the facade); per-file turbo widenings were rejected as fail-dangerous in favor of narrow-or-nothing. A codebase census informed the approved-interface list; only
MaxToolvalidates at registration today, so bouncer hardening for the Temporal worker and the file-system model map is a named follow-up, as is a colocated marker mechanism if the carve-out list ever needs a third entry.