Appendix D — Recorded defects
63 defects are on file against the surfaces this document describes. Each is a case of the software doing something other than what it says, recorded with a reproduction and with what is not established separated from what is.
This list is generated from docs/issues/, so it cannot fall behind the folder.
D.1 /api/ag-ui-chat decodes the JWT without verifying it, and a body flag skips it entirely
Severity high if reachable unauthenticated; the route is caller-facing by design · Status Verified — a forged token is rejected on dev (401); the admin_mode bypass is refused (400); a real token works (200)
docs/issues/20260812_agui_chat_jwt_never_verified.md
D.2 inspection_data returns whole image rows, so storage URLs reach the model and the stream
Severity high · Status Verified — fixed 2026-08-12 (PR pending), re-run 6/6 against the reproduction
docs/issues/20260812_kawa_inspection_data_leaks_storage_urls.md
D.3 six kawa tools query Supabase with the service-role key and no tenant filter
Severity low as deployed (not on the serving path); the code shape remains wrong · Status Fixed 2026-09-10
docs/issues/20260812_kawa_tools_query_without_tenant_scope.md
D.4 the service-role auth gate exempts /ag-ui-chat on a justification that is false
Severity medium — it is why the high-severity issue below went uncaught; no new exposure of its own · Status Verified — the exemption is gone, the route holds no service-role client, and on dev it rejects a forged caller (401)
docs/issues/20260812_service_role_gate_false_exemption.md
D.5 /api/ag-ui-chat is the last chat-surface route still on the service-role key
Severity medium — it writes as an unverified subject (see the coupled issue), through a service-role client that bypasses RLS · Status Verified — converted; on dev the route accepts a real caller (200) and rejects a forged one (401)
docs/issues/20260813_agui_chat_still_uses_service_role.md
D.7 GET /api/organizations/get-members now returns 200-empty to a non-member, not 403
Severity low — a wire-behaviour change, no data exposure either way · Status Fixed 2026-08-25 — decided a defect, not by design: option (2), restore the 403. The handler asks is_org_member_secure before the list read, purely to choose the status code; the read still runs under RLS.
docs/issues/20260813_get_members_returns_empty_not_403.md
D.8 migrations do not reach the database automatically, and db push is blocked by history divergence
Severity medium — operational: merged schema/RLS changes are not live until someone applies them manually, and the standard tool for that is currently broken · Status Confirmed — ownership decided 2026-08-13 (KavApps is the single source of truth, ADR-009); remaining work is the transition
docs/issues/20260813_migrations_not_auto_applied_and_history_diverged.md
D.9 image analysis returns 404 because an ambiguous datasets embed is masked as “Image not found”
Severity high — the “analyze this image” feature is unusable from the web app: every attempt 404s · Status Fixed — root-caused locally and corrected in three routes; PR open
docs/issues/20260814_analyze_404_datasets_embed_ambiguity.md
D.10 human image anomalies have no home in the damage-mechanism catalog
Severity medium — real, on-asset findings cannot be recorded, so they are silently dropped · Status Fixed — typed findings (a finding is grounded in a mechanism OR an observation) landed 2026-08-14; migration + API #605, applied to prod
docs/issues/20260814_image_anomalies_do_not_map_to_the_mechanism_catalog.md
D.11 GET /datasets hands every member the storage account key
Severity — · Status Open — the rotation, and only the rotation. All three serving
docs/issues/20260817_dataset_credentials_returned_to_every_member.md
D.13 deleting an annotation category would delete the findings grounded in it
Severity low as deployed, high the moment a delete is exposed — 4 of the 5 findings on the platform are observation-grounded and would go with their category · Status Fixed 2026-08-25 — supabase/migrations/20260825200000_findings_survive_a_deleted_class.sql changes edm_observation_category_id_fkey from ON DELETE CASCADE to ON DELETE RESTRICT, applied to the shared database and verified. The guarantee is now a schema property rather than a product decision. See the notes at the end.
docs/issues/20260818_deleting_an_annotation_category_deletes_its_findings.md
D.14 any signed-in user can read and write another organization’s gas survey readings
Severity high — a cross-tenant read of survey data, available to the lowest role, and reachable by an AI engine · Status Fixed 2026-08-18 — both policies dropped, so RLS denies every client path; the rows are preserved and the service role is unaffected. Applied and verified on the shared database.
docs/issues/20260818_gas_readings_staging_readable_by_every_tenant.md
D.15 KavApps kap-stable’s migrations reference helpers it never defines, so they cannot replay from empty
Severity medium — no live impact, but the set that ADR-009 calls the source of truth cannot rebuild the schema it is the source of truth for · Status Open — latent; nothing is broken while both repositories share one live database
docs/issues/20260818_kap_stable_migrations_cannot_rebuild_the_schema.md
D.16 five tables let a viewer write, pending a review of who runs ingestion
Severity medium — a viewer can add rows inside their own organization; not a cross-tenant leak and nothing destructive · Status Fixed 2026-08-19 — decided option A for four tables and option D for data_groups, shipped the same day by supabase/migrations/20260819120000_ingestion_writes_require_editor.sql and applied. The file was never re-statused; confirmed against the live database 2026-08-25. See the resolution at the end.
docs/issues/20260819_five_tables_let_viewers_write_pending_ingestion_review.md
D.17 any authenticated user can append to the trace log under any session id
Severity medium — write integrity on an audit log, not a data leak: nothing can be read or altered, only forged · Status Fixed 2026-08-25 — supabase/migrations/20260825180000_message_traces_record_the_caller.sql is applied to the shared database. The RPC now stamps written_by from auth.uid() and written_by_role from auth.role(), so a trace can no longer be appended anonymously under a forged session_id. Not Verified: the harmless reproduction below is still unexecuted, because its middle step is a write. See “Resolution” at the end.
docs/issues/20260819_message_traces_can_be_written_under_any_session_id.md
D.18 any signed-in user can overwrite or delete any other user’s avatar
Severity low — 4 objects, avatars only, no tenant data; but it is a write anyone can perform on anyone · Status Verified 2026-08-24 — fixed by 20260821000000_scope_avatar_writes_to_their_owner.sql
docs/issues/20260820_avatar_writes_are_open_to_every_signed_in_user.md
D.19 decision-log.test.ts fails 5 of 8 assertions on kap-stable
Severity — · Status Open — filed against KavApps kap-stable, not KAP
docs/issues/20260820_decision_log_test_fails_on_its_own_branch.md
D.20 27 handlers on kap-stable still authenticate inline
Severity — · Status Open — forward work (KAP → kap-stable), not a backport
docs/issues/20260820_twenty_seven_handlers_still_use_the_old_auth_idiom.md
D.21 /chat/agui/stream forwards a JWT it never verifies
Severity low — no data reachable without a valid token, but an anonymous caller · Status Fixed 2026-08-23 by 38e15685 — the one line below was applied,
docs/issues/20260821_agui_stream_forwards_a_token_it_never_verifies.md
D.22 GET /images accepts lat, lng and radius, echoes them back, and filters on none of them
Severity medium — no data is exposed that should not be, but a caller asking for images near a point receives every image and no indication that the filter was ignored · Status Fixed — GET /api/images filters through public.images_within_radius (PR #773). The migration was applied to the shared database on 2026-08-21 and verified there: SECURITY INVOKER holds (anon sees 0 rows through the function, the same 0 it sees on the table, against 12,328 for postgres), the radius sweep is monotonic over 27,221 geotagged images. Mirrored to KavApps kap-stable in Soterinc/KavApps#1148; the extern/KavApps pointer bump follows that merge
docs/issues/20260821_get_images_location_parameters_are_not_applied.md
D.23 the annotator rewrote swagger.yaml and deleted all 24 of its hand-written comments
Severity medium — no runtime effect; the loss is reasoning that took a lint run and a triage to establish, and its absence is silent · Status Fixed for the recurrence, not for the instance — annotate_signatures.py now round-trips with ruamel.yaml and refuses to write if the comment count drops (PR #773, decision B3). The 24 deleted lines are still deleted; restoring them is a separate call. Confirmed by diff: 24 present at 5983833, 0 at origin/main, deleted by f2baa8a
docs/issues/20260821_swagger_comments_deleted_by_yaml_rewrite.md
D.24 require_auth checks that a token is present, not that it is valid
Severity low as it stands, and the reason is not in this function — see “Why · Status Open — confirmed by test; no data exposure established
docs/issues/20260823_require_auth_does_not_validate_the_token_it_returns.md
D.25 a URL built with a function call inside it is invisible to the consumer scan
Severity — · Status Fixed 2026-08-25 in #833, folded in beside the two over-reporting defects. Interpolations are matched whole, so a ) inside ${ … } no longer ends the path; pinned by test_a_call_inside_the_url_does_not_truncate_the_path
docs/issues/20260825_a_url_built_with_a_nested_call_is_invisible.md
D.26 an intent told readers GET /gas-readings was not tenant-scoped, and it is
Severity low as a defect, higher as a document — it is the kind of sentence that starts an incident that is not happening · Status Fixed — the sentence is corrected. No behaviour changed, because nothing was wrong with the behaviour
docs/issues/20260825_an_intent_said_an_endpoint_was_unscoped_and_it_was_not.md
D.27 an RLS-bypassing create path, described as internal, is wired to a page
Severity — · Status Open — reported, not fixed
docs/issues/20260825_datasets_create_bypasses_rls_from_a_page.md
D.28 a write operation can be credited with callers that only read it
Severity — · Status Fixed 2026-08-25. The cause first recorded here was wrong — see
docs/issues/20260825_write_operations_credited_with_readers.md
D.29 components/chat/agui-chat-interface.tsx is not mounted anywhere
Severity — · Status Open — reported, not fixed
docs/issues/20260826_agui_chat_interface_has_no_consumer.md
D.30 krml_save_report writes pdf_reports rows nobody’s RLS can read
Severity — · Status Open — reported, not fixed. No evidence found that it is called by anything reachable from the deployed product
docs/issues/20260826_krml_save_report_writes_ownerless_unreadable_rows.md
D.32 three routes classify an image as thermal or RGB three different ways, and /api/images finds no thermal images at all
Severity medium · Status Fixed 2026-08-30 — see Resolution.
docs/issues/20260830_three_thermal_rgb_heuristics_disagree.md
D.33 any signed-in user can load any CAD model they have the URN for
Severity medium — requires a URN the caller was once given, so it is not reachable by guessing; but it survives losing organization membership, and the asset is a customer’s plant model · Status Confirmed — verified against kap-dev on 2026-08-31, not inferred from the code. Not fixed.
docs/issues/20260831_a_viewer_token_outlives_the_org_boundary.md
D.34 kap_cad_query’s unit_code silently matches nothing, because it means a different “unit”
Severity medium — no data exposure and a workaround exists, but the failure is silent and the query is safety-relevant · Status Fixed 2026-08-31 — unit_code removed from kap_cad_query, and equip_no documented as taking a full tag and substring-matching. Scoping decided per asset, so no anchored unit filter is built: the substring behaviour is now stated rather than a trap.
docs/issues/20260831_cad_query_unit_code_means_a_different_unit.md
D.35 candidate priority is a keyword match, and the keywords saturate
Severity low — nothing breaks, but a priority that never varies stops being a priority, on the exact campaign where triage most needs one · Status Open — filed per D5 of the proposal below; not fixed here, and not recommended to be fixed there. Fix direction decided: priority should be a decision (by a user or an AI), not a keyword match. Not yet built.
docs/issues/20260831_candidate_priority_is_always_critical.md
D.36 two CI gates report success while checking nothing
Severity high — not for a defect it has caused, but for the class it can no longer catch: x-service-role is a security claim published to callers, and nothing re-derives it from the handlers any more · Status Fixed 2026-09-01 — both gates read the composed spec and check 160 operations again; the extractor now follows reads into imported helpers, and a write verb only counts when it is chained off a table. 22 annotations across 19 operations were wrong and are corrected. The empty-set guard stays as the backstop.
docs/issues/20260901_annotation_gates_pass_over_zero_operations.md
D.38 kap_cad_query’s “material” was a DGN drawing colour, not construction material
Severity low — no data exposure, but a value labeled “material” reached both AI tools and the real product UI with no source that could back it, on a page integrity engineers use for damage-mechanism judgment calls · Status Fixed, four instances of the same root problem — a “material” claim with no trustworthy source, at four different layers — found and fixed the same day: the CAD drawing-colour bug (migration + API/MCP, PRs #1078/#1080), CadDesignBlock on the asset detail page (PR #1089/#1092), pds_assets.material itself removed from the API/UI/MCP surface (same PRs), and finally the underlying database column cleared (migration 20260901141400, found via Kawa still reading it directly). All four merged, deployed, and verified live. Fully mirrored to KavApps kap-stable (source of truth): both migrations, plus the three readers the rename breaks there and the hand-maintained swagger.yaml that still advertised the old field name (KavApps PRs #1200, #1201 and #1206, all merged). Nothing outstanding here.
docs/issues/20260901_cad_query_material_is_a_drawing_color.md
D.41 certification never writes back to the registry it is measured against
Severity medium — the registry gates which engines the product offers, and it currently asserts the opposite of the newest certification run for dataadk · Status Open
docs/issues/20260902_certification_never_writes_back_to_the_registry.md
D.42 system_type: "dataadk" resolves to a system that is not registered
Severity low today, higher if a client still sends it — no data exposure; the failure is that a retired engine id neither works nor cleanly degrades. · Status Open — found while removing the DataADK copy from KAP (PR #1142), filed rather than fixed there because the fix is a routing decision, not part of the removal.
docs/issues/20260902_dataadk_system_type_resolves_to_nothing.md
D.43 the deployed product falls back to an engine that cannot certify, and declares it verified
Severity medium — no user-visible failure is proven, but the fallback is what every user without a stored preference receives, and it is the one engine that fails three mandatory certification rows including server-side scope enforcement · Status Open — KAP side fixed (#1129); the deployed side is untouched
docs/issues/20260902_deployed_fallback_engine_is_not_certifiable.md
D.44 a tampered JWT ends the run RUN_FINISHED, not RUN_ERROR
Severity medium — a failed authentication is reported to the client as a successful run, so an expired or invalid session is indistinguishable from “you have no data”. No data is exposed; the failure is that the client cannot tell it failed. · Status Open — for the AI team. Found in KavApps main (a97ff172), running locally on :8091. Not reproduced on KAP’s engines, which pass this row.
docs/issues/20260902_tampered_jwt_finishes_the_run_instead_of_erroring.md
D.45 the in-frame mark misses the asset — the registry sits ~5 m low, and two flights’ altitudes are ~11 m high
Severity medium — the Tier 3 (framed) link and the review sheet’s crosshair are trustworthy at long range and wrong by a vessel-length up close; nothing an engineer has decided rests on them yet. · Status Calibrated 2026-09-08 (KAP #1202) — the camera is moved into the registry’s frame by data/pose-calibrations/polycarbon-unit-audit.yaml; open for the 20:3x flight (needs its own reconstruction) and for plate boxes. Two separable causes, both quantified below; neither is the photogrammetric reconstruction.
docs/issues/20260908_asset_registry_heights_and_flight_altitude_bias.md
D.46 The AI Assistant declines a gas-threshold locate question instead of answering it
Severity medium · Status Open
docs/issues/20260917_ai_assistant_declines_gas_threshold_locate_query.md
D.47 FR-APP-14’s “hard ship gate” is not enforced — no branch protection exists on KavApps main, and the workflow’s own “advisory” framing for Tier 3 doesn’t match its exit code
Severity medium · Status Open
docs/issues/20260917_tier3_eval_gate_is_not_actually_a_hard_gate.md
D.48 the assistant opens with “Yes” to a leading question, then states the correct constraint
Severity medium · Status Open — needs a product decision before it can be called a defect
docs/issues/20260921_a_conditional_yes_to_a_leading_question.md
D.49 the judge agrees with the assistant, and the verdict is thrown away for citing no evidence
Severity high · Status Open — two defects, the second needs a protocol-version decision
docs/issues/20260921_a_negative_dimension_cannot_quote_evidence.md
D.50 a fabrication pattern matches the phrase an answer quotes in order to deny it
Severity high · Status Open
docs/issues/20260921_forbidden_patterns_fire_inside_an_explicit_denial.md
D.51 material is null for all 109 assets, so six tasks can only test abstention
Severity high · Status Open — the benchmark side is repaired; the data gap is not
docs/issues/20260921_material_is_null_for_every_asset_in_the_register.md
D.53 asked what it remembers on an empty thread, the assistant invents an answer
Severity high · Status Open — tracked in GitHub #411, which found it first. This record adds the 2026-09-21 reproduction and corrects a claim it made on that date.
docs/issues/20260921_the_assistant_invents_conversation_history.md
D.54 four API suites ran against a mock while believing they held a real database
Severity high · Status Fixed — all four. The gas_readings “RLS finding” was a broken fixture (2026-09-24)
docs/issues/20260923_four_api_suites_were_testing_a_mock.md
D.55 npm run test:e2e tests whatever is already on port 3000
Severity medium · Status Open
docs/issues/20260923_test_e2e_tests_whatever_is_on_port_3000.md
D.56 28 of 63 E2E tests assert against routes and labels the app no longer has
Severity medium · Status Open
docs/issues/20260923_the_e2e_suite_has_drifted_from_the_app.md
D.58 —
Severity — · Status —
docs/issues/20260924_the_asset_register_takes_40_s_to_paint.md
D.59 —
Severity — · Status —
docs/issues/20260924_the_centre_framed_rule_has_no_home.md
D.60 —
Severity — · Status —
docs/issues/20260924_the_dev_server_degrades_over_a_single_e2e_run.md
D.62 every image thumbnail is readable by anyone who can guess its URL
Severity high · Status Verified — bucket private since 2026-09-26 00:20 (KAP #1379 + KavApps #1290 deployed, KavApps #1289 applied)
docs/issues/20260925_the_thumbnails_bucket_is_public.md
D.63 <one line stating what is broken, not what should happen>
Severity high | medium | low · Status Open
docs/issues/TEMPLATE.md