Replace category drilldown with a search bar over a flat type list.
GET /types gains a flat=true param that flattens categories server-side
(custom types included); useActionItems(true) opts into it while every
other caller keeps the nested default.
launch_enricher took payload.sketch_id on trust: any authenticated caller
could dispatch an enricher against a sketch belonging to someone else, and the
worker would write its findings into that sketch.
EnricherService gains get_sketch_for_launch, the same lookup launch_flow has
used since ac485e2c: resolve the sketch, then _check_permission for "update"
on its investigation. No role row means no access, so an unknown sketch 404s
and a viewer or a stranger gets 403, matching the flow route's responses.
The check runs before the graph read, so a denied caller never reaches Neo4j
and never queues a task.
Unlike FlowService's copy, the sketch id is parsed to a UUID first.
SketchRepository.get_by_id binds it as one, so a non-UUID string reached
SQLAlchemy and raised, which would have turned a bad id into a 500 instead of
a denial.
EnricherCreate/EnricherRead/EnricherUpdate had no importer anywhere in the
repo and were the whole content of app/api/schemas/enricher.py, so the module
goes with them.
get_enrichers' custom-type branch returns [] before reaching the
wobbly_type=True registry listing. The early return stays (why a published
custom type lists no enrichers is a separate question); the line it strands
does not.
EnricherBase.set_params() had no caller and no dynamic-dispatch site;
resolve_params/async_init assign self.params directly.
Without known_first_party, isort's own-package detection depends on
which files it's given: sorted alone, a file inside flowsint_enrichers
gets flowsint_enrichers imports grouped separately from flowsint_core/
flowsint_types; sorted together with the rest of the workspace (as
`make lint` and CI do), they collapse into one group. lint-staged only
ever passes the staged subset, so a commit could pass the pre-commit
hook locally and still fail `make lint` in CI — silently, since both
are "isort says it's fine," just about different file sets.
Pin the four packages as known_first_party in all four pyproject.toml
so grouping is the same regardless of invocation scope. The rest of
the diff is isort re-normalizing ~100 files to the now-consistent
order — import statements only, verified against the diff.
Flow update, delete, and ID-based read never checked caller identity
against the target record, so any authenticated user could tamper with
or delete another user's Flow by UUID, and the list endpoint returned
every user's flows unfiltered. Added a nullable owner_id column (NULL
= pre-existing flows, readable by all but not editable via the API),
stamp it on create, and check ownership in FlowService before mutating
or returning a single Flow.
Fixes GHSA-qvx2-6v5v-wwrg.
The Scan model exposes details (JSON) and error (Text) columns, but the
enricher/flow tasks assigned to scan.results — a non-mapped attribute.
SQLAlchemy accepted the assignment silently, so commit() never persisted
it and the data was lost. Success paths now write scan.details; failure
paths write scan.error. ScanCreate.results renamed to details to match.
Scan repository tests pass.
deps.py read AUTH_SECRET via os.getenv() before load_dotenv() ran,
yielding None when the secret was only present in .env. Import the
already-validated AUTH_SECRET from core.auth (loads dotenv first and
raises if unset) instead of re-reading it. Removes the dup + ordering bug.
Add POST /api/types/detect endpoint that detects the type of a text input
by iterating registered types and calling their detect() classmethod.
Returns detected type with fields and primary field pre-filled.
Falls back to Phrase when no type matches.