-
Notifications
You must be signed in to change notification settings - Fork 0
Concerns
Provenance. Generated by
/n8-mapfrom the commit named below, and not regenerated since. Individual statements may have been overtaken by later work — most of the issues this map cites have since been fixed and closed. Read it as a snapshot of the codebase's shape, not as a current defect list; the GitHub issue register is authoritative for what is actually open. (The generating commit hash predates a history rewrite and no longer resolves.)
Map-level tech debt, fragile areas and design concerns for AutoNate — the structural view that the 87 per-finding audit issues (archived-7–archived-93) do not capture, plus a small set of new, demonstrated findings.
Generated from commit 01f0f174 on 2026-08-31 by /n8-map.
Scope: src/AutoNate.Web (ASP.NET Core net10.0), src/AutoNate.Spa (React 19 / Mantine v9 / Vite), src/AutoNate.Plugin.Abstractions + plugins/, services/hocuspocus, services/executor, flowable-extension/, infra/, tests/. Sibling maps: Stack, Conventions.md, Testing.md.
Open issues grouped by theme. Numbers only; see GitHub for text. The last column is the structural cause the theme shares.
| Theme | Issues | Structural cause |
|---|---|---|
| Executor sandbox (Pyodide/isolated-vm) | archived-58, archived-64, archived-69, archived-39, archived-49 |
services/executor is a hand-rolled NATS worker with no lockfile, no supervisor, no compose entry (see §4.5); the Python path was bolted onto the JS runner. |
| Agent skills bypass authorization gates | archived-19, archived-20 |
IAgentSkill implementations call stores directly; nothing forces a skill to go through IAuthorizer the way RequireKindPermission forces endpoints. |
| Authorization fails open / inert grants | archived-59, archived-23, archived-22, archived-21, archived-25, archived-24, archived-45 |
AuthorizationOptions defaults are permissive and live only in appsettings.Development.json; EntityKinds/Actions are string constants with no compile-time link to routes, so grantable ≠ enforced (§2.7). |
| SSRF / caller-controlled hosts | archived-60, archived-61 | Admin-supplied URLs are trusted because "admin" is treated as fully trusted; there is no outbound-host policy layer. |
| Error text leakage | archived-68, archived-20 | Endpoints fold ex.Message into responses; no shared problem-details mapper. |
| Request-body / OOM levers | archived-67, archived-63 | Global MaxRequestBodySize = 1 GiB (Program.cs:928) instead of per-endpoint limits; parsers materialise whole files. |
| Content-Type / download sanitiser inconsistency | archived-65 | Two download paths (page attachments vs datastore files) implemented separately. |
| Local infra unauthenticated | archived-66 | Compose stack is dev-shaped; NATS/JetStream has no auth config and is published on all interfaces. |
| Plugin isolation gaps | archived-62, archived-70, archived-63 |
plg_readers grants SELECT on all of public (§2.5); ALC lifecycle on failure is unwound by hand. |
| Stability: unbounded / untimed loops | archived-71, archived-72, archived-73, archived-74, archived-75, archived-76, archived-77, archived-78 | Background services and sidecars each own their retry/timeout policy; no shared resilience helper (Polly/HttpClient timeouts) and no cancellation discipline (CA2016 is off, archived-41). |
| Hot-path performance | archived-51, archived-52, archived-53, archived-54, archived-55, archived-57, archived-56 | Flowable is read live per request (§2.2); several list endpoints materialise then filter; DataTable mode="auto" probes twice. |
| Dead / orphaned surfaces | archived-43, archived-44, archived-46, archived-47, archived-48, archived-50, archived-31, archived-30, archived-42 | Features shipped in "phases" (see test names archived-33) leave half-wired endpoints, columns and menu stubs behind. |
| Docs / skill drift | archived-26, archived-27, archived-28, archived-29, archived-49 | Project skills and header comments are hand-maintained prose with no check against code (§2.9). |
| Lint / analyzer ratchets | archived-41, archived-32, archived-40 | Analyzers run but warnings are not errors (Directory.Build.props); ESLint uses a numeric warning budget (§4.2). |
| Dependency CVEs | archived-34, archived-35, archived-36, archived-37, archived-38 | No CI, so npm audit / Dependabot output is never enforced. |
| 508 — notes module | archived-8, archived-9, archived-10 | Notes UI predates the Mantine migration and uses hand-rolled modals/rows instead of Mantine Modal/Table. |
| 508 — shell / shared components | archived-11, archived-12, archived-13, archived-14, archived-15, archived-16, archived-17, archived-18, archived-7 | No route-change focus management or document-title convention; DataTable and MenuTreeEditor expose mouse-only affordances. |
| No CI / test gaps | archived-79, archived-80, archived-81, archived-82, archived-83, archived-84, archived-85, archived-86, archived-87, archived-88, archived-89, archived-90, archived-91, archived-92, archived-93, archived-33 |
.github/workflows/ does not exist; ~1,400 tests run only on one machine; gate-presence tests prove a gate exists, not that it denies. |
-
Issue:
src/AutoNate.Web/Persistence/DatabaseSchemaInitializer.csis one static class: ~78private const string …Sqlblobs (82CREATE TABLE IF NOT EXISTS, 48ALTER TABLE, 44 of themADD COLUMN IF NOT EXISTS) executed in a fixed order byEnsureAsync(:3746). There are no EF migrations anywhere (grep -r 'MigrateAsync\|\[Migration' src= 0). The only versioning construct isauth_seed_state(:424), a key/timestamp latch used for data seeds (role_permissions_to_grants_v1:636,site_config_*:1113…2269,main_menu_data_v1:3177), not for DDL. Column type changes, renames and drops cannot be expressed idempotently — the fiveDROPsites (:653DROP TABLE role_permissions,:680,:764,:2412,:2735) are one-shot and unrecorded. -
Files:
src/AutoNate.Web/Persistence/DatabaseSchemaInitializer.cs;src/AutoNate.Web/Persistence/AutoNateDbContext.cs(76DbSets across three partials; 77 POCOs underPersistence/Scaffolded/);infra/postgres/init/02-create-autonate-app-schema.sql(a second copy of the schema replayed by both test fixtures —tests/AutoNate.Web.Tests/PostgresTestDatabase.cs:370,tests/AutoNate.E2E.Tests/AutoNateE2EFixture.cs:269);src/AutoNate.Web/Program.cs:1039-1048(initializer loop). -
Impact: (a) EF model ↔ SQL drift is undetectable — the only schema-existence test is
tests/AutoNate.Web.Tests/AgentFoundationSchemaTest.cs, which checks four table names; (b)02-create-autonate-app-schema.sqland the initializer must be kept in sync by hand; (c)EnsureAsyncruns with nopg_advisory_lock, so two hosts booting concurrently race through theDO $$ … $$read-then-write blocks (unverified, static analysis only — the advisory lock atServices/DataStores/Sql/DatastoresDatabaseInitializer.cs:114covers only the datastores writer role); (d) the file has 57 commits of churn out of 337 and grows with every feature. -
Fix approach: Do not rewrite to EF migrations wholesale. (1) Add a
schema_versionledger and split the const blobs into ordered, numbered steps applied once each — the in-repo pattern already exists for plugins:src/AutoNate.Web/Plugins/PluginMigrationRunner.cs:87(__plugin_migrations+ lexically orderedmigrations/*.sql); reuse it for the host by pointing it atsrc/AutoNate.Web/Persistence/Migrations/NNN_*.sql. (2) Takepg_advisory_lock(hashtext('autonate-schema'))at the top ofEnsureAsync. (3) Add one test that boots the host and, for everyDbSetinAutoNateDbContext.Model.GetEntityTypes(), runsSELECT * FROM <table> LIMIT 0— this catches column drift in both directions. (4) Deleteinfra/postgres/init/02-create-autonate-app-schema.sqlfrom the fixtures and let the host initializer create the test schema, so there is one source.
-
Issue: The executions UI reads Flowable live — every route in
src/AutoNate.Web/Endpoints/ExecutionEndpoints.csinjectsIFlowableClient(GET /api/executions:35,/page:96with in-memory paging), andIFlowableReadThrough(Services/Flowable/Cache/FlowableReadThrough.cs) is registered atProgram.cs:988but injected by zero endpoints. AQL entities, dashboards and the authorization selector compiler read the Postgres cache instead (Services/Query/Entities/WorkflowExecutionsQueryEntity.cs:101,Authorization/Selectors/WorkflowExecutionCacheSelectorCompiler.cs), which is refreshed by a 60 s poll (Services/Flowable/Cache/FlowableExecutionPollingFeed.cs,FlowableCache:ExecutionPollInterval). The projection'sTenantIdis hard-codednull(FlowableExecutionProjection.cs:226). -
Files: above, plus
Services/Flowable/FlowableClient.cs(1,761 lines),Persistence/DatabaseSchemaInitializer.cs:3271(WorkflowCacheSchemaSql),docs/projection-framework/*.md. -
Impact: A list page and a dashboard widget can disagree for up to a minute; permission filtering for executions is evaluated against the cache while the row the user clicks comes from Flowable; archived-52's O(n) fetch exists because the endpoints ignore the cache. Any future "cold tier" (
Program.cs:993-1003) widens the gap. -
Fix approach: Make the cache the read model for lists (
GET /api/executions[/page]→WorkflowExecutionsQueryEntity/FilterQueryAsynconWorkflowExecutionCache, SQL-side paging likeGET /api/recordsusesBuildRecordSqlFilter), keep Flowable for detail/actions, and useFlowableReadThroughon cache-miss. This also resolves archived-52. Register a backfill source so the cache can be rebuilt (§4.1).
-
Issue: A fully functional dev stack is 12 processes — 9 containers (
infra/ensure-up.sh:31-41REQUIRED_SERVICES: postgres, flowable, flowable-dapr, redis, nats, nats-init, dapr-placement, dapr-scheduler, hocuspocus) plus hostdaprd,dotnet, and Vite — and that still omitsservices/executor(§4.5). Redis exists only as the Dapr state store (infra/dapr/components/statestore.yaml; zeroStackExchange.Redisreferences insrc/). Dapr is used for pub/sub over NATS JetStream (infra/dapr/components/pubsub.yaml) yet the host never usesDaprClient—Services/Events/AuditOutboxDispatcher.cs:161POSTs raw HTTP to/v1.0/publish/..., whileServices/Nats/NatsStreamProvisioner.csandServices/Nats/NatsConnectionProvider.cstalk to NATS directly. The JetStream stream shape is asserted from two places that must agree:NatsStreamProvisioner.cs:55-91andinfra/scripts/bootstrap-jetstream.sh. -
Files:
infra/docker-compose.yml,infra/ensure-up.sh,infra/dapr/components/*.yaml,Makefile,Services/Signals/DaprStreamingSubscriber.cs,Services/Events/AuditOutboxDispatcher.cs,Services/SystemHealth/SystemHealthService.cs:77-141. -
Impact: Every environment (CI included — there is none, archived-79) must reproduce this topology. Dapr adds a second hop and a second failure mode (
healthprobeself-healing atDaprStreamingSubscriber.cs:309-322; thedeliverPolicy: newincident recorded inpubsub.yaml) for a message path that already has a native NATS client in-process.GET /api/health/systemprobes 8 components but not Hocuspocus (§4.4). -
Fix approach: Decide the bus once. The in-repo direction is already NATS-native (
NatsConnectionProvider,JetStreamCodeNodeRunner,pipeline-code-runsstream); movingAuditOutboxDispatcher,DaprStreamingSubscriberandflowable-extension/.../DaprWorkflowEventPublisher.javato direct JetStream publish/consume removes daprd, placement, scheduler, flowable-dapr and Redis (five of nine containers). Until then, add Hocuspocus and the executor toSystemHealthServiceso the health page reflectsREQUIRED_SERVICES.
-
Issue: 1,636 lines, 85 commits of churn. Beyond DI wiring it contains four hand-maintained registries that must move together (§3.1), a ~93-line dev auto-login middleware (
:1150-1242),POST /account/login(:1304-1396) and/account/logout(:1404-1419) as inline lambdas, two WebSocketapp.Maphandlers (:1253,:1292), three static helpers afterapp.Run()(:1576-1636), the/api404 guard (:1549-1558), 24AddHostedServicecalls, ~40IAgentSkillregistrations (:618-730) and startup validation for secrets (:779,:793). -
Files:
src/AutoNate.Web/Program.cs. -
Impact: Every feature touches the same file (merge conflicts, no ownership boundary); the login endpoint is the only endpoint not in
Endpoints/*and so is missed by conventions likeRequireKindPermissionaudits andAuthorizationGatePresenceTests. -
Fix approach: Extract per-area
IServiceCollectionextension methods following the existingAddProjectionFramework/AddProjectionpattern (Program.cs:942-987) and move/account/*intoEndpoints/AccountEndpoints.csshaped like the canonical handler in Conventions §1.3. Keep the four registries adjacent and add the parity test from §3.1.
-
Issue: Plugins load in a collectible
AssemblyLoadContext(Plugins/PluginAssemblyLoadContext.cs:32). Type identity across the boundary depends on a five-entrySharedAssemblieslist (:16-27: Abstractions, DI.Abstractions, Logging.Abstractions, Npgsql, Dapper) that must match the exclusion list inplugins/Directory.Build.targets:19-27— two hand-maintained lists with no test or build check.AutoNate.Plugin.Abstractions.csprojhas no<Version>;PluginAssemblyLoadContext.Loadmatches on name only (:39), so any plugin binds the host's current abstractions regardless of what it compiled against, and breakage surfaces at enable time (PluginRuntime.cs:131-262).plg_readersreceivesSELECT ON ALL TABLES IN SCHEMA publicplus default privileges (DatabaseSchemaInitializer.cs:1557-1565, archived-62).IPluginContext.HostServicesis an allow-list wrapper (SafePluginServiceProvider,PluginRuntime.cs:248). -
Files:
src/AutoNate.Web/Plugins/PluginAssemblyLoadContext.cs,PluginRuntime.cs,PluginSchemaProvisioner.cs,plugins/Directory.Build.props,plugins/Directory.Build.targets,src/AutoNate.Plugin.Abstractions/*.cs. -
Impact: Adding a type to the abstractions surface that returns a host type (the
NpgsqlConnectioncase the comment at:21-24describes) silently duplicates that assembly unless both lists are edited; theHelloPluginbuild output already shipsNpgsql.dll/Dapper.dllintobin/(stripped only at zip time). No compatibility version means no way to refuse an old plugin gracefully. -
Fix approach: (1) Generate one list: an MSBuild item in
plugins/Directory.Build.propsand aSharedAssemblies.txtembedded resource read by the ALC, or a test intests/AutoNate.Web.Tests/Plugins/that parsesDirectory.Build.targetsand asserts set equality withPluginAssemblyLoadContext.SharedAssemblies. (2) Add<Version>to the abstractions csproj and anabstractionsVersionfield toPluginManifest; reject on major mismatch inPluginUploadValidator. (3) Replace the blanketplg_readersgrant with explicit per-table grants (archived-62).
-
Issue: Notes/pages use BlockNote (
src/lib/yjs/useBlockNoteWithYjs.ts, fragment"document-store":87); documents use@eigenpal/docx-editor-*@1.0.3(ProseMirror;src/components/documents/DocxDocumentEditor.tsx, fragment"default":773). Both ride the singleuseYjsDocumenthook (src/lib/yjs/useYjsDocument.ts:49) and the same Hocuspocus sidecar, whoseservices/hocuspocus/src/materializers.tsmust know each fragment name to producebody_jsonbsnapshots. docx-editor needs a CSS button reset (DocxDocumentEditor.css:16-23) and a forced remount on role change (:838). -
Files: above;
services/hocuspocus/src/{materializers,persistence}.ts;src/lib/blocknote/pageSchema.ts. -
Impact: Two ProseMirror lineages (TipTap via BlockNote, raw PM via docx-editor) with a pinned
@tiptap/coreoverride inpackage.jsonto keep them from colliding; two sets of AI/agent integration paths (BlockNote custom extension planned vs docx-editor's bundled agent panel); Excalidraw (useYjsExcalidraw.ts) is a third Y-doc shape. Any Hocuspocus materializer change needs to be tested against three document kinds. -
Fix approach: Do not unify the editors. Instead centralise the contract: one
YjsDocumentKindsmodule (SPA) mirrored by one table inmaterializers.tskeyed by document-name prefix (note:,page:,napkin:,diagram:, docx) listing fragment name + materializer; add a hocuspocus unit test that fails if a prefix has no materializer.
-
Issue:
Authorization/AuthorizationOptions.csdefaults toEnabled=false,Enforcement="off",AssignSuperAdminToAllExistingUsers=true,DryRun=false;appsettings.jsonhas noAuthorizationsection — onlyappsettings.Development.json:75-80turns it on (full).Enforcementis read at four sites inAuthorization/Evaluator/Authorizer.cs(:113,:242,:330,:379);DryRunconverts only write denials (MaybeDryRun:218-226) and never applies toFilterQueryAsync/SQL filters. The test factory forcesEnabled=false/Enforcement=off(tests/AutoNate.Web.Tests/AutoNateWebApplicationFactory.cs:52-84), so most endpoint tests never exercise enforcement (archived-87). -
Files: above;
Program.cs:286-287. -
Impact: Beyond archived-59 (fail-open default), the shape means every new gate is written and tested in a mode where it is inert;
read-onlymode produces lists that are filtered but rows that are writable, which is not a state any test asserts. -
Fix approach: Make
Enforcementa required value with no default (fail startup if unset, mirroring the secret validation atProgram.cs:779), add a second test factory preset withfullenforcement used by the no-grant→403 tests (archived-87), and dropDryRunor extend it to the read path so it means one thing.
-
Issue: No tenant/organisation concept exists —
grep -ri tenant src/AutoNate.Webfinds only Flowable's pass-throughworkflow_execution_cache.tenant_id(DatabaseSchemaInitializer.cs:3279), always writtennull(FlowableExecutionProjection.cs:226). Isolation is expressed via grants (Authorization/), the content hierarchy (Project → Cabinet → Notebook → Page), and per-plugin Postgres schemas. Plugin DB roles,plg_readers,local_users,site_settings,menusare global. -
Files:
Persistence/DatabaseSchemaInitializer.cs,Authorization/EntityTypes/CoreEntityTypes.cs,Plugins/PluginSchemaProvisioner.cs. -
Impact: Multi-tenancy later would touch 82 tables, every store's
Where, all selector compilers and the plugin role model — a rewrite, not a feature. Nothing today prevents the assumption from silently spreading. -
Fix approach: Record the decision explicitly in
.n8/decisions.md(single-tenant; one deployment per tenant) so planning does not assume otherwise; if multi-tenancy is ever required, start atIRequestContext+AutoNateDbContextglobal query filters, not at the tables.
-
Issue:
docs/plans/(9 dated files, kept deliberately —.n8/decisions.md) cites 17 paths that do not exist (2026-05-30-data-stores-implementation.mdalone: 13 of 34);README.md:129saysappsettings.jsonshipsAllowedHosts: "*"(it ships"",appsettings.json:15);README.md:156documentsAUTONATE_DATA_ROOT, which nothing reads (the knob isData:Root,Storage/DataOptions.cs:9);Agents.codex.mdinstructsnpx playwright testagainstlocalhost:5173for a suite that is Playwright .NET (tests/AutoNate.E2E.Tests/AutoNate.E2E.Tests.csproj:11) on a random port;.claude/skills/*drift is already archived-26–archived-28.docs/mantine/llms.txtis a vendored snapshot. -
Files:
README.md,Agents.codex.md,docs/plans/*.md,.claude/skills/*/SKILL.md,docs/projection-framework/*.md. - Impact: An executor agent following README/Agents.codex.md runs the wrong commands; plan files read as delivered structure when they are proposals.
-
Fix approach: Delete
Agents.codex.md(superseded byCLAUDE.md+ this wiki); add a header lineStatus: historical proposal — see this wikito eachdocs/plans/*.md; fix the two README facts; enforce auto-memoryfeedback_skill_drift(verify a skill's steps when it is invoked) with atests/check that everypathmentioned in aSKILL.mdexists.
-
Issue: Eight option sections are bound in
Program.csbut appear in neither appsettings file:AuditOutbox(:204— unset ⇒DirectPublishAuditEventOutbox, i.e. the outbox is bypassed by default,:212-221),ContentAttachments(:377),DocumentImports(:381),DataStores:Sql(:470),Agent(:747),SystemIssues+ nine detector sub-sections (:828-863),Data(:901).Flowable,Dapr,Nats,WorkflowBehaviors,YjsServer,Authorization,ConnectionStringsexist only inappsettings.Development.json, so a non-Development host starts withFlowable:BaseUrl = "".Features:ScopedSubscriptions(Services/BusWatcher/Subscriptions/ScopedSubscriptionsOptions.cs:10) is never bound at all. Only threeEnvironment.GetEnvironmentVariablesites exist (Program.cs:67,:72,:1024). - Impact: Operators cannot discover knobs from the shipped config; the default outbox mode differs from the mode the stability audit reviewed (archived-71 assumes the EF outbox is on).
-
Fix approach: Ship every bound section in
appsettings.jsonwith its default value and a one-line comment key ("//AuditOutbox"pattern already used insrc/AutoNate.Spa/package.json"//overrides"), deleteScopedSubscriptionsOptions, and addValidateOnStart()to the sections that are required outside Development (pattern:Program.cs:776-796).
-
Issue:
BuildSpaistrueonly forRelease(AutoNate.Web.csproj:17-18); Debug never populateswwwroot/, so the static-file middleware,MapStaticAssets, the/api404 guard andMapFallbackToFile(Program.cs:1513-1571) are all skipped in the configuration developers run. The E2E fixture is the only path that exercises them and it does so bydotnet run -p:BuildSpa=trueafter deletingsrc/AutoNate.Web/wwwroot/(tests/AutoNate.E2E.Tests/AutoNateE2EFixture.cs:320).flowable-extensionbuilds inside the Flowable image (infra/flowable/Dockerfile), Node is pinned only in Dockerfiles (node:22-alpine; noengines/.nvmrc; host runs v24.15.0). -
Impact: Production-only middleware has no unit-level coverage; the 405-vs-404 gap documented at
Program.cs:1564-1570and theUseStaticFilescomma-filename workaround (:1515-1525) are invisible in dev. -
Fix approach: Add a
WebApplicationFactorytest variant that setsWebRootPathto a temp dir containing anindex.html, and assertGET /api/nope→ 404no-store,GET /some/spa/route→index.html. Pin Node via"engines"in all threepackage.jsonfiles.
-
Files:
Program.cs:288-295(entity kinds),:299-337(16ISelectorCompiler+ registry),:339-348(10IInstanceAuthorizer),:402-437(11 AQL entities, each registered twice — concrete +IQueryEntityforward);Authorization/EntityTypes/CoreEntityTypes.cs:23-30,AnalyticsEntityTypes.cs. -
Why fragile: No assembly scanning. Drift fails silently: a kind without a selector compiler makes
Authorizer.FilterQueryAsyncreturnsource.Where(_ => false)with only a WARN (Authorization/Evaluator/Authorizer.cs:267-275); an AQL entity registered as concrete but not forwarded toIQueryEntityyields404 Unknown entityfrom/api/aql/schema/entity(Endpoints/AqlSchemaEndpoints.cs:36-39); a missing instance authorizer yieldsDeny("no instance handler…")(Authorizer.cs:126-131). Only a duplicate(Kind, ClrType)fails loudly (Authorization/Selectors/SelectorCompilerRegistry.cs:18-22).tests/AutoNate.Web.Tests/Authorization/EntityRegistryTests.cs:10-33hard-codes17kinds and nothing cross-checks the other three lists. Seven core kinds (Plugin, SystemIssue, SiteConfig, Project, Cabinet, Notebook, Page) have no compiler by design (content kinds go throughIContentAuthorizer,Program.cs:354-355). -
How to change safely: Follow
.claude/skills/add-permission-gate/add-projection(after fixing archived-26). Add all four registrations in one commit and extendEntityRegistryTestswith a parity assertion: for every kind inIEntityRegistrythat is not in an explicitContentKinds/NoQueryKindsallow-list, assert anISelectorCompilerand anIInstanceAuthorizerresolve. Never register an AQL entity only as the concrete type.
-
Files:
Program.cs:1549-1558(insideif (Directory.Exists(app.Environment.WebRootPath))at:1513); rationale:1529-1548;MapFallbackToFileregex:1571. -
Why fragile: It must sit after all
Map*Endpoints()(:1421-1487) and afterUseStaticFiles/MapStaticAssets(:1526-1527) sohttp.GetEndpoint()is populated, and beforeMapFallbackToFile. Turning it into aMapFallback("/api/{**rest}")route re-introduces theAcceptsMatcherPolicyregression (body-lessPOST /api/system-issues/{id}/resolve→ 404) and tripsAuthorizationGatePresenceTests. It does not run in Debug (nowwwroot/), and non-GET unknown/apipaths return 405, not 404 (:1564-1570). -
How to change safely: Keep it middleware (auto-memory
reference_test_suite_infra). If it must move, extract toMiddleware/ApiNotFoundMiddleware.csand register it at the same position; add the §2.11 test first so the ordering is pinned.
-
Files:
Plugins/PluginAssemblyLoadContext.cs:16-27;plugins/Directory.Build.targets:19-27;plugins/Directory.Build.props:20-24(Private=falseon the abstractions reference only). -
Why fragile: Adding a host type to
IPluginContextthat lives in a new assembly requires editing both lists; missing the ALC side loads a second copy and everyis/cast across the boundary fails at runtime insidePluginRuntime.cs:131-262(reported as a SystemIssue, not a build error). Missing the targets side only bloats the zip. -
How to change safely: Edit both files in the same commit, rebuild
plugins/HelloPlugin, runtests/AutoNate.Web.Tests/Plugins/PluginLoaderTests.csandPluginDataIsolationTests.cs(they loadtests/AutoNate.Web.Tests.SamplePlugin). Add the parity test from §2.5.
-
Files:
src/components/documents/DocxDocumentEditor.tsx:773("default"),src/lib/yjs/useBlockNoteWithYjs.ts:87("document-store"),services/hocuspocus/src/materializers.ts,services/hocuspocus/src/persistence.ts; document-name prefixes atVisualTextEditor.tsx:86,PageOverview.tsx:61,NapkinEditor.tsx:108,DiagramEditor.tsx:137,useYjsNotesList.ts:62. -
Why fragile: The fragment name is a string agreed between SPA and sidecar; a rename breaks
body_jsonbsnapshots silently (comment atDocxDocumentEditor.tsx:756-758) while live collaboration keeps working, so the failure appears only on version restore / search.ensure-up.shrebuilds the hocuspocus image on a content hash ofsrc/**— editing the SPA side alone does not trigger it. -
How to change safely: Change SPA and
materializers.tstogether, runmake infra-ensure(forces the sidecar rebuild), thenDocumentEditorTests/notes E2E (archived-89 makes this a 3 s sleep — expect flakiness).
-
Files:
src/components/documents/DocxDocumentEditor.tsx:829-838(key${importMode}:${role}),:810(externalPluginsmemo deps includerole),src/lib/yjs/useYjsDocument.ts:64(roledefaults to"viewer"). -
Why fragile: docx-editor latches
readOnlyat mount; removing the key leaves an editor read-only after the server promotes the role. MovinguseYjsDocumentinside the keyed component would tear down the provider on every remount and lose the IndexedDB/WS session. -
How to change safely: Keep the Y.Doc/provider one level above the keyed element; if upgrading
@eigenpal/docx-editor-reactpast 1.0.3, check whetherreadOnlybecame reactive and only then drop the key. Re-check the button reset (DocxDocumentEditor.css:16-23) after any upgrade — auto-memoryfeedback_docx_editor_button_reset.
-
Files:
src/AutoNate.Spa/vite.config.ts(nooptimizeDeps, nomanualChunks, proxy →http://localhost:5108,server.watch.ignoredforpublic/drawio/**),package.jsonoverrides(@tiptap/corepin). -
Why fragile: Installing or bumping BlockNote / docx-editor / Excalidraw leaves stale
node_modules/.vite/deps, surfacing as504 Outdated Optimize Depon lazy chunks; the@tiptap/coreoverride must be re-validated on every BlockNote bump or TypeScript sees two incompatible@tiptap/coretypes. -
How to change safely:
rm -rf node_modules/.vite/deps && npm run dev -- --forceafter such installs (auto-memoryfeedback_vite_force_after_heavy_deps); runnpx tsc -b --forceafter clearingtsconfig.*.tsbuildinfobefore believing any type result (auto-memoryfeedback_unused_ts_module_verification).
-
Files:
src/AutoNate.Spa/src/pages/workflow/WorkflowStudio.tsx:2190,:2198(see §4.3). -
Why fragile: The user's shell aliases
greptougrep -I, which silently skips the file (exit 1, no "binary file matches" line). Every "zero importers" or "unused export" claim based on grep is wrong for the 3,916-line file that imports the most modules in the SPA; the audit rejected four such findings for exactly this reason (.n8/decisions.md). -
How to change safely: Use
command grep,perl -ne, orgit grep -a; treat trial-delete +tsc -b --forceas the only proof of dead code. Fix the root cause per §4.3.
-
Files:
tests/AutoNate.E2E.Tests/AutoNateE2EFixture.cs:320(Directory.Delete(src/AutoNate.Web/wwwroot, recursive: true)),:329(deletes static-web-asset manifests undersrc/andtests/),:251-259(DROP DATABASE AutoNate_E2E),:132-168(dotnet run -p:BuildSpa=true). -
Why fragile:
dotnet test AutoNate.sln(README:106) includes this project, so a full solution test run rebuilds the SPA and wipeswwwroot/— a Release-published tree loses its assets. Both fixtures hard-codelocalhost:5432/autonate(PostgresTestDatabase.cs:41; only the password and, for E2E, the port are overridable). -
How to change safely: Run backend tests as
dotnet test tests/AutoNate.Web.Testsand E2E asmake e2e; never point either at a non-throwaway Postgres.
-
Files:
Program.cs:1022-1037(Development-only sidecar probe; throws unlessAUTONATE_ALLOW_RUNNING_WITHOUT_DAPR=true),:1039-1048(initializers),:1054-1058(NatsStreamProvisioner.EnsureStreamsAsync),infra/start-autonate-web-sidecar.sh(fails if 3500/50001 are held by anything else). -
Why fragile: Schema init runs before the pipeline exists, so a schema failure is a crash with no
/api/health; NATS stream provisioning runs after schema init but before any hosted service, and disagreement withinfra/scripts/bootstrap-jetstream.shsurfaces asnats: no response from streamat first publish, not at startup. -
How to change safely: Keep the order; when adding a stream/subject edit both
NatsStreamProvisioner.cs:55-91andbootstrap-jetstream.shand add it toLegacyStreamsToRemove(:113) if a subject moves.
None of these duplicate an open issue title in archived-7–archived-93.
-
Issue:
BackfillRunner.RunGenericAsyncresolvesIProjectionBackfillSource<TSource>from DI and throwsInvalidOperationExceptionwhen none is registered (src/AutoNate.Web/Services/Projections/BackfillRunner.cs:58-63); the endpoint maps that to 400 (Endpoints/AdminProjectionsEndpoints.cs:72-93). No class in the repo implementsIProjectionBackfillSource<>(grep -rn IProjectionBackfillSource src tests plugins→ only the interface,BackfillRunner, and two comments;FlowableExecutionBackfillSourcenamed atServices/Flowable/Cache/FlowableExecutionPollingFeed.cs:14does not exist). The SPA exposes a Rebuild button for each projection (src/AutoNate.Spa/src/pages/admin/Projections.tsx:176→src/api/projections.ts:47). -
Files: above;
docs/projection-framework/operations.mddocuments rebuild as the recovery step. -
Impact: The documented recovery path for a corrupted or retention-truncated cache (
workflow_execution_cache,workflow_task_cache,workflow_variable_cache,workflow_event_log_cache,record_activity_rollup_cache) does not work; admins see a red "No IProjectionBackfillSource<…> registered" toast. Adjacent to archived-47 (reset-watermark) but distinct. -
Fix approach: Implement
IProjectionBackfillSource<WorkflowExecutionSummary>overIFlowableClient.GetWorkflowExecutionsAsync(the polling feed already has the enumeration), register it next toAddProjectionatProgram.cs:953-966, repeat for the task/variable/event-log/rollup sources, and add aProjectionFrameworkPhase4Tests-style test asserting 200 for each name inGET /api/admin/projections. Until then, hide the button whenfeedsis empty or return 501 with a clear message. -
Evidence: A temporary xunit test (
tests/AutoNate.Web.Tests/ZzTmpRebuildProbeTests.cs, deleted afterwards;git statusclean) bootedAutoNateWebApplicationFactory, listedGET /api/admin/projections(5 projections, all with"feeds":[]), and POSTed/rebuildfor each:REBUILD flowable.workflow_execution_cache -> 400 {"ok":false,"message":"No IProjectionBackfillSource<WorkflowExecutionSummary> registered for projection 'flowable.workflow_execution_cache'."} REBUILD flowable.workflow_task_cache -> 400 …<FlowableTaskSummary>… REBUILD flowable.workflow_variable_cache -> 400 …<FlowableInstanceVariables>… REBUILD flowable.workflow_event_log_cache -> 400 …<FlowableHistoricActivityEvent>… REBUILD records.record_activity_rollup_cache -> 400 …<RecordActivityRollupSnapshot>…
-
Issue:
src/AutoNate.Spa/package.json:11sets"lint": "eslint src --max-warnings=411". The current tree emits exactly 411 warnings and 0 errors, so the next warning anywhere failsnpm run lintwhile nothing today is required to go down. Breakdown:react/no-unescaped-entities234 (130 inpages/admin/config/PluginDocumentation.tsx),react-hooks/exhaustive-deps40 (5 inpages/notes/NotesPage.tsx, 4 inpages/notes/ProjectSettingsModal.tsx, 2 incomponents/data-table/DataTable.tsx),jsx-a11y/*97 (archived-40 territory),@typescript-eslint/no-unused-vars24, and 12 unusedeslint-disabledirectives (8 inwidgets/AutoConfigForm.tsx, pluslib/yjs/commentAudit.ts:21,lib/yjs/useYjsExcalidraw.ts:101,pages/dashboard/WidgetConfigDrawer.tsx:99,pages/dashboard/WidgetHost.tsx:95). -
Files:
src/AutoNate.Spa/package.json,src/AutoNate.Spa/eslint.config.js, files above. -
Impact: The ratchet is a ceiling, not a ratchet — it blocks new code without shrinking debt; 40 stale-closure warnings are hook-correctness bugs waiting to happen (
DataTable.tsxis shared by every list page). The 12 unused directives are free to delete and are exactly the kind of thing archived-32 (missing reasons) will trip over. -
Fix approach: Fix
react/no-unescaped-entitiesmechanically ('/{"'"}) — that alone drops the ceiling to 177; then lower--max-warningsin the same PR every time (true ratchet). Treatreact-hooks/exhaustive-depsas an error once the 40 are triaged. Add--report-unused-disable-directivesto the lint script. -
Evidence:
npx eslint src -f jsonfromsrc/AutoNate.Spa→ 0 errors, 411 warnings (counted from the JSON);npm run lintexits 0.
-
Issue:
src/AutoNate.Spa/src/pages/workflow/WorkflowStudio.tsx:2190and:2198build aMapkey as`${topic}<NUL>${eventType}`with a literal0x00byte in the source (not\0/�). It is the only tracked text file in the repo containing a NUL. Under the shell'sgrep(a function wrappingugrep -I) the file is silently skipped:grep -c useState WorkflowStudio.tsxprints nothing and exits 1, whilecommand grep -c useStateprints 24. -
Files:
WorkflowStudio.tsx:2186-2200(knownEventsmemo). -
Impact: Every grep-based audit, dead-code claim and refactor search misses the largest file in the SPA (3,916 lines, one exported component, 19 private modal components, 24
useState); the/n8-auditrun rejected four false "zero importers" findings for this reason, and the next agent will hit it again. -
Fix approach: Replace both bytes with
"�"(or a visible separator such as""/"::"sincetopic/eventTypeare identifiers) in one commit; add an ESLint rule (no-irregular-whitespacedoes not catch NUL — useno-control-regex-style custom check or atests/guard that fails on\x00in tracked text files, e.g. theperlone-liner below in a pre-commit hook). -
Evidence:
perl -ne 'print "$.:" . ($_ =~ tr/\0//) . "\n" if /\0/' WorkflowStudio.tsx→2190:1,2198:1; repo-wide scan ofgit ls-filesfor\0in text files → only this file.
4.4 Production SPA bundle: a single 4.46 MB entry chunk (1.29 MB gzip), no code-splitting configuration
-
Issue:
vite.config.tshas nobuild.rollupOptions/manualChunksand only 8lazy(sites exist in the SPA. A production build emits 196 JS chunks totalling 14 MB, led byindex-*.js4,458 kB / 1,291 kB gzip,chunk-EIO257PC-*.js1,821 kB / 744 kB,dist-*.js897 kB / 251 kB, plus three more chunks above 500 kB. -
Files:
src/AutoNate.Spa/vite.config.ts; route table insrc/AutoNate.Spa/src/(8lazy()calls);package.json(BlockNote, docx-editor ×4, Excalidraw,@xyflow/react,recharts,@svar-ui/react-filemanager, CodeMirror ×5,sucrase). -
Impact: Every first load pulls 1.3 MB gzip of JS before
/api/auth/me; every deploy invalidates the whole bundle; theMapStaticAssetsETag workaround atProgram.cs:1515-1525exists partly because assets are so few and so large. -
Fix approach: Lazy-import the four editor stacks at the route boundary (
DocumentEditorPage.tsxalready does this for docx-editor — extend the pattern to Notes/BlockNote, Excalidraw, WorkflowStudio, dashboards) and addmanualChunksformantine,blocknote,docx-editor,excalidraw,codemirror; setbuild.chunkSizeWarningLimitonly after those land. -
Evidence:
npx vite build --outDir <scratch>fromsrc/AutoNate.Spa(output removed afterwards), sizes read from the build log; Vite's own(!) Some chunks are larger than 500 kBwarning printed.
4.5 services/executor is not part of the local stack, and the health page cannot see Hocuspocus — unverified (static analysis only)
-
Issue:
grep -c executor infra/docker-compose.yml infra/ensure-up.sh→ 0 and 0; theMakefilehas no target for it. The executor has aDockerfilebut no compose service, soJetStreamCodeNodeRunner(Services/Pipelines/Execution/) has nothing consumingpipeline-code-run.>in the documented dev stack. Separately,Services/SystemHealth/SystemHealthService.cscontains no reference to Hocuspocus or Yjs (grep -ci 'hocuspocus\|yjs'→ 0) althoughensure-up.sh:39lists it as required and all notes/pages/documents/diagrams depend on it. -
Files:
infra/docker-compose.yml,infra/ensure-up.sh:31-41,services/executor/Dockerfile,Services/SystemHealth/SystemHealthService.cs:77-141. -
Impact: A pipeline code node in dev waits for a reply that never comes (behaviour not exercised here — archived-69 covers the executor's own failure modes); a Hocuspocus outage leaves
/api/health/systemfully green while every Y.Doc load fails. -
Fix approach: Add an
executorservice toinfra/docker-compose.yml(build../services/executor,NATS_URL=nats://nats:4222) and toREQUIRED_SERVICES; add aCheckHocuspocusAsyncTCP/HTTP probe toSystemHealthServicemirroringCheckDaprControlPlaneAsync(:125-141) and an executor liveness check via a NATS request to apipeline-code-run.pingsubject.
-
Issue: (a)
tests/AutoNate.Web.Tests/AutoNateWebApplicationFactory.cs:61sets"Flowable:BaseAddress", butFlowableOptionshas onlyBaseUrl(Configuration/InfrastructureOptions.cs:7) — the key binds to nothing and tests run withBaseUrl = ""(masked becauseIFlowableClientis swapped forStubFlowableClient). (b)ScopedSubscriptionsOptions(Services/BusWatcher/Subscriptions/ScopedSubscriptionsOptions.cs) declaresFeatures:ScopedSubscriptionsand is referenced nowhere else. (c)README.md:156documentsAUTONATE_DATA_ROOT; the only occurrence in the repo is that line. (d)Agents.codex.mdprescribesnpx playwright testandlocalhost:5173; the suite is Playwright .NET on a random port. - Files: above.
- Impact: Misleading to the next executor; (a) means any test that ever un-stubs Flowable will fail with an empty base URL.
-
Fix approach: Rename the key to
Flowable:BaseUrl; deleteScopedSubscriptionsOptions.cs; fix README toData__Root; deleteAgents.codex.md. -
Evidence:
grep -rnresults listed above;dotnet build AutoNate.slnis otherwise clean (0 errors, 2 warnings — bothS3398intests/AutoNate.Web.Tests/Authorization/Document*AuthorizerTests.cs).
Every item in §4 plus the design concerns in §2 the owner chose to track were filed by /n8-map after independent re-verification (see .n8/decisions.md):
| Issue | Concern |
|---|---|
| #112 (sev:high) | Projection rebuild returns 400 for every projection — no IProjectionBackfillSource<> implemented (§4.1) |
| archived-113 (sev:medium) |
infra/ensure-nats-stream.sh narrows the stream on every make infra-ensure
|
| archived-114 (sev:medium) |
services/executor absent from the local stack (§4.5) |
| archived-115 (sev:medium) | No Hocuspocus probe in SystemHealthService (§4.5) |
| archived-116 (sev:medium) | NUL bytes in WorkflowStudio.tsx make grep skip it (§4.3) |
| archived-117 (sev:medium) | 3.9 MB entry chunk, no code splitting (§4.4) |
| archived-118 (sev:low) | Lint cap is a ceiling; 12 unused eslint-disable directives (§4.2) |
| archived-119 (sev:low) |
IFlowableReadThrough registered, injected nowhere |
| archived-120 (sev:low) | README AUTONATE_DATA_ROOT / Rider run-config drift (§4.6c) |
| archived-121 (sev:low) | Test factory sets Flowable:BaseAddress (§4.6a) |
| archived-122 (sev:low) |
ScopedSubscriptionsOptions never read (§4.6b) |
| archived-123 (sev:low) |
Agents.codex.md prescribes a Playwright JS suite that doesn't exist (§4.6d) |
| archived-124 (spike) | Schema init: no advisory lock, no version ledger (§2) |
| archived-125 (spike) | Executions: Flowable live vs workflow_execution_cache (§2) |
| archived-126 (spike) | Runtime dependency surface — Redis only for Dapr state, outbox → Dapr HTTP hop (§2) |
Rejected during re-verification (not filed): "audit outbox is bypassed by default" — AuditOutboxOptions.Enabled defaults to true (Services/Events/AuditOutboxDispatcher.cs:16), so the durable outbox is the default path.
Getting started
Using Auton8
- Records
- Workflows
- Documents-and-Notes
- Queries-and-Dashboards
- Data-Stores-and-Pipelines
- The-Assistant
- Administration
Building Auton8
Repository