Mindbridge Β· track status

AMA-DISCLOSE

Content reaches someone who should not see it β€” a failure nobody experiences until it is catastrophic.

Re-measured 2026-08-26 Β· the open count resolves live at build. πŸ›‘ Fourteen rows below have closed β€” six on 2026-08-16 (#3283, #3284, #3285, #3135, #3141, #3128), four on 2026-08-17 (#3118, #3325, #3155, #2824), four on 2026-08-18 (#3117, #3295, #3367, #3368) β€” marked inline rather than deleted, because the cohort's shape is the argument. 2026-08-19β†’25: the graph-integrity wave landed β€” #3389 and #3466 closed (PRs #3476, #3475), #2905 closed by owner ruling against ADR-147, #3128 REOPENED as p0 and its remediation applied as a production write, and the carrierless-write side is stopped (#3488). Thirteen track PRs merged in the window; see History. Verify each issue is still open before starting it β€” this queue moves without notice. 2026-08-26: the org-dimension landed data-only β€” the internal/external person classification (PR #3584, effect deferred to #3595/#3596 below); also merged: the confidence-floor SQL choke-point (PR #3583), viewer_user_id on disclosure audit rows (PR #3578), and the blind-viewer-job-function invariant detector (PR #3565) β€” none closed an issue.

Next β†’πŸš¨ The tenant-readable cache (#3431) is now the top item β€” raised to p1 on 2026-08-30. doc_need_matches carries exactly one policy, (tenant_id = tenant_id()), with no clearance arm β€” and the library_doc_needs RPC is SECURITY INVOKER (prosecdef = FALSE), so it reads that table under the caller's RLS. That single weak policy is the only thing between a tenant user and the cache. Then the #3386 enforce-flip decision β€” still the owner gate, though demoted p0 β†’ p2 on 2026-08-30: the blast radius measured at 2 mis-gating nodes, not the 3,875 its body assumes. Its shadow gate is live (PR #3568) with a coverage denominator; flipping it enforcing needs the 5 named steps in that PR. Behind them: #3534 Stage B (message-scoped impersonation classification β€” Stage A shipped), #3582 (unverified DOCUMENT-fold widening candidate). #3581's design fix has shipped β€” rollup totals are derived at check time, no longer stored.
Re-derived 2026-08-25 β€” 4 open, and the 2026-08-19β†’25 wave changed the shape again: the rendered-leak queue is empty and the read-side carrier work landed, so what is open is write-side residue and enforcement decisions. Closed in the window: the pre-ADR-089 OFFER passthrough (#3389, PR #3476 β€” the read side; the write side is #3488) Β· the trait-shape choke-point (#3466, PR #3475, state:not-in-effect for the un-recreated TRAIT nodes) Β· the facts-fail-open p0 (#2905 β€” closed by owner ruling 2026-08-24 against ADR-147, already shipped and verified live) Β· both audience forks (#3367/#3368, PR #3408 β€” before this window). Reopened: #3128 as a p0 β€” the identity cutover's backfill left Regina 1,685 at-risk bare nodes under-gating live; the remediation ran 2026-08-25 as a production write (see the row). Open and shipping: #3386 (shadow gate live, enforce-flip owner-gated β€” the head owner gate; the #3118/#3128 backfills also wait on the owner) Β· #3534 Stage B Β· #3431 Β· #3582 Β· #3465 Β· the org-dimension pair #3595/#3596 (the classification landed 2026-08-26 data-only; its effect is deferred to the per-tenant backfill and the owner-sequenced gate). #3488 closed 2026-08-26: the writer proven clean on live traffic (209 new enterprise LABELED nodes in 24h, 0 carrierless) and the standing population resolved by the dedup wave's hard deletes down to 112 backfill-impossible dead-weight rows.

🩸 The 2026-08-19 finding that reframed the queue (kept for the record): a legacy text[] trait shape was dropped at the read boundary, so a fold produced no traits and the prune deleted the tenant's existing TRAIT nodes as absent β€” no error, and nothing distinguishing it from a legitimate departure. Measured: 338 bare-string elements across 196 rows in 5 tenants; demo holds 120 all-legacy rows and 0 TRAIT nodes. Fixed at the reader in PR #3464; the shape choke-point shipped as PR #3475 β€” the deleted TRAIT nodes are the state:not-in-effect residue.

πŸ›‘ Still deferred: #3118's 2,023-row carrier backfill and #3128's structural backfill remain unrun-and-owner-gated; #3386 carries it until its shadow gate flips enforcing (owner gate, five named steps); #3466 until TRAIT recovery is measured or scoped out. #3488's label came OFF 2026-08-26 when live traffic proved the writer clean.
4Open issues
2,023Answer nodes Β· NULL provenance
43.1%Offer nodes Β· no message anchor
82Regina pairs Β· incomparable audiences
Live leaks Model-call leaks The rule Suppressed Β· discharged Why this order Rulings History

Open work

Open work β€” the live fail-open first, then carrier correctness (2026-08-19)

Sequenced in docs/plans/20260819-graph-integrity/README.md (revision 3). πŸ›‘ Read Β§0 and Β§8 before acting on it: revisions 1 and 2 each had their central recommendation inverted by an adversarial panel, both times in the fail-OPEN direction, both times because a cited mechanism's polarity had not been read to the end. The measurements survived all three rounds; the reasoning built on them failed twice.

p1 #3389 Pre-ADR-089 need-response OFFER fails open β€” LABELED type takes the structural passthrough and serves verbatim response_text ungated βœ… CLOSED 2026-08-19 β†’ PR #3476 β€” read side; write side is #3488 Enterprise

βœ… Closed 2026-08-19 β€” PR #3476 flips audience_is_structural to an allowlist of NAME_STYLE_NODE_TYPES (fail-closed for future node types) and deletes the hand-spelled passthrough OR-leg from viewer_needs_you's problem/opportunity lanes β€” closing the class-wide verbatim-text fail-open across all nine LABELED types. No flag, no backfill: the effect landed at deploy.

🩸 Two honest residues its own body stated: the production blast radius (how many nodes stop rendering) was unmeasured at merge β€” and four days later #3488 found the WRITE side still producing carrierless LABELED nodes (~1,223 in 7 days) β€” which is why this class lived on as its own row, #3488 below, since closed 2026-08-26 with the writer proven clean on live traffic. The description below is the row as it read while open.

A carrierless need-response OFFER is a LABELED type, so it took FULL_STRUCTURAL_NO_AUDIENCE and served verbatim response_text to any viewer within the tier ceiling, while ANSWER β€” in VERBATIM_TEXT_NODE_TYPES β€” failed closed. That asymmetry is why the graph-integrity plan's revision 1 had to be thrown away; see its Β§0.

p1 #3387 Node text and conversation_* spec text drift apart across re-extraction β€” breaks every reconstruct-by-text backfill why #3128 healed only 21.5% Dev only

The conversation_* spec tables are re-derived on every enrichment run; nodes_v2 rows persist. So a content node's text is a snapshot of whichever extraction created it, while the spec row it came from has since been rewritten.

backfill_content_node_conversation_identity.py reconstructs a node's conversation by matching canonical_key(spec.text) == node.canonical_name (whitespace-collapse + casefold). Once re-extraction reworded the source, that match cannot succeed β€” which is the measured root cause of the #3128 backfill healing only 21.5%, and therefore of #3386 existing at all.

πŸ”‘ The general lesson worth more than the issue: any backfill that reconstructs identity by matching TEXT is betting on text that another pipeline rewrites. The graph-integrity plan proposes re-scoping or closing this row (Β§5 Step 6) β€” the carrier-audience route in #3386 does not reconstruct by text at all.

p2 #3431 doc_need_matches is readable by any tenant user with tenant-only RLS β€” the same cache class as library_files_mat sibling of the #3427 fix Enterprise

doc_need_matches is a precomputed cache, GRANT SELECT … TO authenticated, whose only policy is tenant_id = public.tenant_id() β€” tenant only, no clearance, no audience. Identical in shape to the defect #3427 fixed on library_files_mat and library_documents_mat.

πŸ”‘ Found by censusing the RULE rather than the artifact. The first census keyed on table_name LIKE '%_mat' and was structurally blind to this table β€” a clean result from a query that could not have found it. Filed at p2 because the exposure is weaker than the library caches, not because the shape is different.

p2 #3478 find_attachment scope enforcement fails closed and SILENT β€” 'no results' is byte-identical whether nothing exists or everything was scope-blocked AMA-TRUST handoff Β· blocks ADR-148's coverage ledger ruling Dev only

Handed over from AMA-TRUST (measured 2026-08-19 on the live lane): _enforce_scope zeroes every scope-gated find_attachment result when the driving context carries viewer=None β€” a diagnostic walk saw all 15 well-ranked xlsx candidates dropped to zero β€” and _no_results_message is byte-identical whether the corpus is empty or everything was blocked. A real signed-in user always resolves a full viewer, so this is not the live user path; it is a completeness-honesty gap and a misuse trap for any CLI/eval/harness driving AMA as a user (it cost that fleet two confounded repro attempts). ADR-148's coverage-ledger ruling waits on a decision here.

p0 #3386 Merged content nodes keep a WRONG carrier after the #3128 backfill β€” 3,875 nodes gate against another conversation's audience Step 0 MEASURED (89.8% leaking) Β· shadow gate live β€” enforce-flip is an owner gate Enterprise

Both halves moved in the 2026-08-19β†’25 wave. Step 0 ran: the merged-node population is 2,760 nodes / 4,762 provenance conversations, 89.8% LEAKING (2,171) / 0% OVER-HIDING β€” the direction is measured, and it is the dangerous one. Step 1 shipped in two pieces: the provenance-intersection primitive merged as PR #3485 (which its own docstring said had NO CALLER), and the wiring landed 2026-08-24 as PR #3568 β€” a shadow-comparison harness inside evaluate_for_serving, observe-only, after the audit row, with a per-call coverage-denominator log line.

πŸ›‘ The gate is NOT enforcing, and flipping it is an owner gate this PR explicitly did not ask for. What remains before a flip is defensible, per the PR: close or bound the coverage gap Β· gather a divergence rate over real traffic with the denominator Β· wire the SQL lane (serving_scope_projection.py) Β· demonstrate provable no-op on the ~87,000 non-merged nodes Β· the flip itself. Separately, the #3128 remediation of 2026-08-25 nulled scope on the at-risk bare population (1,702 nodes fleet-wide) β€” a fail-closed stopgap, not this row's audience-intersection fix.

πŸ”‘ provenance_conversations is a monotone UNION across merges, so the leaking population grows with time. The earlier plan doc (docs/plans/20260819-graph-integrity/, revision 3) sequenced this; its Β§0/Β§8 warnings about inverted polarity still apply to whoever flips the gate.

p1 #3466 No choke-point for trait-entry shape: three sites know it, one is SQL βœ… CLOSED 2026-08-19 β†’ PR #3475 β€” TRAIT recovery not-in-effect All

βœ… Closed 2026-08-19 β€” PR #3475 shipped exactly the structural fix this row asked for: the 338 legacy bare-string elements normalised into the object shape, and a DB CHECK (via an IMMUTABLE helper) makes the bare-string shape impossible for every writer β€” the shape knowledge now sits in zero application sites, with the reader's string branch kept only as a documented safety net. Measured at merge: 338 elements across 196 rows in 5 tenants.

πŸ›‘ Carries state:not-in-effect: the migration does not recreate the TRAIT nodes already deleted by the pre-#3464 prune (demo: 120 legacy rows, 0 TRAIT nodes) β€” that needs a separate re-materialisation pass, and the label comes off only when that recovery is measured or scoped out, per the PR's own contract.

p2 #3465 Extraction drops malformed personality traits silently β€” unmeasurable by construction needs a counter, NOT a policy change All

Filed 2026-08-19. _drop_malformed_traits discards entries with no counter and no log. πŸ›‘ A dropped entry is never stored, so it leaves no trace to query β€” if the model is emitting the wrong trait shape today we are losing traits on every extraction and nothing would ever tell us. Invisible from the data side by construction; only instrumentation can answer it.

πŸ›‘ Not a request to coerce here. Coercing at extraction manufactures the evidence-free trait nodes #1275 exists to prevent. The asymmetry with the read path is deliberate: at extraction the grounding is obtainable and the model can be re-prompted; at read time the evidence is unrecoverable and the alternative is deletion.

p1 #3390 Re-materialize the merged content nodes #3128 cannot split 🩸 MEASURED AND REFUTED as a repair route Enterprise

πŸ›‘ Superseded by measurement, 2026-08-19 β€” this route does not work and the issue should be re-scoped or closed. Two production pilots were run. Re-materialization does not split the merged nodes; separately it exposed that a graph-materialization start phase under-produces β€” on the demo tenant a fold deleted 49 nodes, every TRAIT node among them, while passing the mass-delete fraction guard at 3.4%.

The prune's guard reads a fraction and cannot see composition. _PRUNE_GUARD_MIN_TENANT_NODES = 50 is an exemption (small tenants skip the fraction check), not a protection β€” and it is not what let demo through. The needed guard refuses when the victim set wipes ~100% of any node type, and must be blocking with a named override and a threshold validated against prior clean runs.

p1 #3488 Carrierless LABELED content nodes are still being WRITTEN β€” ~1,223 new in 7 days; #3389 closed only the read side βœ… CLOSED 2026-08-26 β€” writer proven clean on live traffic Enterprise

βœ… Closed 2026-08-26, label off. The write side stopped by PR #3569 is proven on live traffic: with ingestion resumed, 209 enterprise LABELED nodes were created in 24h, 0 of them carrierless (personal: 1,237 created, 0 carrierless) β€” the control the paused-pipeline state never had. The standing population resolved itself differently than the PR's backfill plan assumed: the concurrent dedup wave (PR #3573's duplicate collapse) hard-deleted the bulk of it, leaving 112 enterprise rows (regina 111, demo 1) β€” pre-fix, fail-closed INVISIBLE dead weight for which backfilling the carrier is impossible, per the close. The intermediate check this page recorded on 08-25 (3,574 ≀ 3,588 baseline, fix holding) was superseded within a day by the stronger control.

p1 #3534 sender_origin_verdicts keyed by raw from_email collides real + spoofed senders Stage A shipped (PR #3548) Β· Stage B design owed Enterprise

Filed 2026-08-23 off the live Kareem Abou Ghaly incident (PR #3535 repaired his node in production: 26 spoofed messages reassigned off his identity, the verdict row deleted). Stage A shipped same-week β€” PR #3548 requires full corroboration before suppressing an impersonated person: both verdict tables flip from ANY-message to EVERY-message, so a person with any clean message survives. Proven live on the Kareem case.

πŸ›‘ Stage B remains: a real message sharing the identical from_email as the spoofed ones still suppresses β€” that needs message/fingerprint-scoped classification, tracked as a comment in the migration, not yet designed.

p2 #3581 suppression_baseline.json's redundant total header desyncs on concurrent merges and wedges main βœ… design fix shipped β€” rollups derived at check time, not stored Dev only

🩸 Proof by incident, 2026-08-25: two concurrent PRs (#3568, #3569) each regenerated the suppression baseline against pre-merge main; the hand-baked total header said 667 while the per-file counts summed to 668, and every PR after that went red β€” main wedged, deploys blocked β€” until PR #3580 regenerated the header. Any two concurrent PRs that each added a suppression reproduced this under the stored-header format.

βœ… Design fix shipped: both rollup headers (total and unjustified β€” same lossy shape) are no longer stored in the baseline; the gate derives them from counts at check time, so disjoint-key concurrent regenerations auto-merge coherently and legacy headers are ignored, not rejected. Documented residual, both directions: two branches moving the SAME (file, rule, kind) leaf git-dedupe to one value. Bump collisions undercount β†’ gate reds main (loud; one regeneration recovers). Drain collisions OVERCOUNT β†’ gate stays green with an inflated ceiling and unjustified growth inside the slack passes ungated β€” silent, nothing prompts regeneration; structural to any committed-counts gate under git's identical-leaf dedupe (owner follow-up). debt_baseline.json shares BOTH directions (owner decision pending).

p2 #3582 DOCUMENT-node audience fold keyed on content_hash may widen beyond any single carrier β€” unverified candidate filed 2026-08-25 β€” needs its Step-0 Enterprise

Filed 2026-08-25. The DOCUMENT-node audience fold keys on content_hash β€” the same key that makes attachments 2..N of a busy message disproportionately files the graph has already seen β€” so a document's computed audience may be wider than any single carrier intended. Unverified by measurement: it needs the same classification treatment #3386's Step 0 got (which direction does it fail, and how big is the population now that the attachment recovery added 76,228 rows?). Kin to the unfiled second fail-open candidate recorded on #2905's close.

p1 #3595 run the org-membership backfill per tenant β€” the classification PR's effect lands only when this runs state:not-in-effect β€” effect deferred All

πŸ›‘ MERGED IS NOT LANDED. The internal/external person classification (PR #3584, merged 2026-08-26) changed no row anywhere: nothing automatic writes properties.org_membership, and new person nodes stay unclassified until a run picks them up. This issue carries state:not-in-effect until every tenant's backfill is measured done β€” the label comes off then, not when the script runs.

Per tenant, by an operator: dev_tools.py --tenant <slug> backfill-org-membership (dry-run first; --recompute re-classifies). Chunked and resumable β€” a mid-run crash re-selects unclassified nodes (NOT (properties ? 'org_membership')). Grounded only in deterministic signals: owner identity, a WORKS_AT edge to the owner's own company, owner email domain (generic providers excluded at owner-set build time). Unknown never collapses to external. Open owner decision inside this row: one-off per tenant vs a periodic recompute cadence.

p1 #3596 disclosure gate keyed on org_membership β€” owner-sequenced, price the blast radius first depends on #3595 Β· must ship flag-gated in shadow mode Enterprise

The consumer of the attribute: gate disclosure audiences on organisational membership instead of bare job_function β€” which carries no organisational dimension at all. Measured on Regina 2026-08-24: 30 person nodes carry executive and exactly ONE works at Pasta Regina; the other 29 are executives at counterparties, so any policy keyed on job_function alone hands an org-relative privilege to mostly-external people.

πŸ›‘ Blast radius to price FIRST (why this is owner-sequenced): 1,605 of 1,794 job-function-carrying people are external (89%), and β‰₯26,212 messages have an external sender whose function currently feeds scope.job_functions. Gating on the attribute narrows those audiences β€” fewer viewers, silently β€” an over-hiding regression that must ship behind a flag in shadow mode with coverage measurement, never as a cutover. unknown must fail OPEN at the gate (the classifier's contract, enforced again at consumption).

Live disclosure leaks β€” all closed as of 2026-08-18
p0 #3283 The Hidden Connections feed card renders offer/need titles with no audience check β€” live on /api/feed/intelligence, and ungateable as written βœ… CLOSED 2026-08-16 (PR #3336) Enterprise

βœ… Closed 2026-08-16 β€” gated on the audience axis and legacy connection provenance healed (PR #3336). Kept here because it was this page's Next call and, until it closed, the only client-reachable live leak; the description below is the row as it read while open. The remaining client-reachable render leak is #3155, two rows down.

use-feed.ts β†’ /api/feed/intelligence serves offer/need titles from the denormalized connections table with no carrier pointer, so disclose() has nothing to rule on β€” ungateable as written. The other row sets in the same function are gated; connection_rows is not. The rows are written by ConnectionDetectionPhase with as_user=False during ingestion β€” correctly ungated at write time (no viewer exists then); the gate is owed at read time and absent. Enforcement is already on in production. The fix is a decision: give the table provenance (carrier message per side), or re-derive the node ids at read time and join messages. Sibling of #3141; related to #3128.

p0 #3155 traverse_graph renders the RAW canonical_name in a candidate's title at ABSTRACTED/ATTRIBUTED βœ… CLOSED 2026-08-17 (PR #3397) Enterprise

βœ… Closed 2026-08-17 β€” the title now derives from the disclosure level on BOTH render branches (PR #3397): FULL keeps the raw title; ABSTRACTED/ATTRIBUTED substitute the row's already-projected variant (no new reads); variant-absent withholds the row; the media branch swaps the disclosed title and its alt_text echo below FULL. Two panel rounds; over-suppression measured empty. The same shape on other tools is #3391. The description below is the row as it read while open.

The body is de-identified at ABSTRACTED/ATTRIBUTED but the title renders raw β€” _candidate_display_title returns display_name (falling back to canonical_name) unconditionally, computed before the disclosure level is read, so a below-floor viewer reads the real item name. Applies to every candidate _format_graph_candidates renders (hop 1, hop 2, retrieval), not one path. Sibling surfaces do not have this shape β€” _gated_node_text / _format_connection_pairs degrade the one string they render.

p0 #3128 TypedItemMaterializer reduces a multi-message node's audience to one arbitrary carrier (stable_min_uuid) β€” under-gating live on Regina ↩️ REOPENED 2026-08-24 β†’ remediation APPLIED 2026-08-25 Enterprise

↩️ Closed 2026-08-16 (PR #3355), reopened the week of 08-24 as a p0. The conversation-scoped identity cutover was right, but its one-time backfill could not reach everything: PR #3560's exposure census (verified read-only against all three still-open tenants, counts matching exactly) found Regina 1,685 Β· Hassan Abozeid 15 Β· Sudqi 2 at-risk bare content nodes still under-gating live β€” and 239 Regina nodes whose anchor is unresolvable, so true exposure may exceed the reported number.

🟒 The remediation ran 2026-08-25 as a production write (the PR's script, owner-approved): scope nulled on 1,702 nodes fleet-wide β€” fail-closed, the leak ends as INVISIBLE rather than exposure β€” plus 21,368 SAME_CONTENT_AS sibling links and 13,138 duplicate bare-node deletes (the PR #3567/#3573 tooling). A 4,187-row doc_need_matches-blocked follow-up remains.

πŸ›‘ Why the issue stays open: nulling scope is a stopgap that trades the leak for over-hiding β€” the properly re-scoped population needs the #3386 audience-intersection machinery, and state:not-in-effect stays until the rows are measured repaired. The full repair campaign (fold β†’ re-merge β†’ unpause) is the REGINA-DEDUP page's story. The row as it read while open: a multi-message node's audience was reduced to one lexically-minimum carrier, discarding 232 contributing audiences across 155 multi-message groups; the identity cutover made one node per (type, canonical text, conversation) so a node cannot span audiences by construction.

p0 #2905 AMA: facts fail OPEN on missing audience while messages fail CLOSED β€” conversation-anchored facts skip disclosure βœ… CLOSED 2026-08-24 by owner ruling β€” already fixed, verified live All

βœ… Closed 2026-08-24 by owner ruling, against ADR-147. The re-investigation found the p0 was already designed and shipped β€” ADR-147 (PR #3392, merged 2026-08-17): labeled no-carrier/no-scope nodes fail CLOSED via the NAME_STYLE allowlist, and disclosure enforcement was verified true on the live app machine. A p0 retired by discovering it was already fixed and verifying that against the running code. Its close recorded a candidate successor it did not file: DOCUMENT nodes with a non-NULL own properties.scope under a NULL anchor may be a second fail-open β€” kin to #3582 below.

The asymmetry that founded it, kept for the reader: facts failed OPEN on a missing audience (100% of answer and 43.1% of offer nodes had no message anchor) while messages failed CLOSED; of 4,489 conversations with more than one scoped message, 3,921 (87.3%) had mixed audiences.

p1 #3325 A document's audience can be decided by an arbitrary carrier message (ADR-145 D4) βœ… CLOSED 2026-08-17 β€” two lanes forked All

🟒 The Documents Archive half shipped 2026-08-17 (PR #3366) β€” library_documents now intersects disclose_scope_is_visible over every tier-visible carrier of the document's content_hash, with an unknown audience failing closed. Most-restrictive-wins is now a recorded standing ruling on ATTACHMENT-TRUTH.

πŸ›‘ It reached one of three lanes, and its own panel filed the other two β€” #3367 and #3368 below. Until those land, the Archive withholds a document that Files, Needs, Memories and the Ask lane still disclose.

The defect: a document's audience fell back to an arbitrary carrier message β€” a COALESCE fallback biting documents with no document-level scope; document_scope wins when present, and sensitivity_tier is document-level and unaffected. The attachment recovery backfill grows the affected population, so it could not ship undecided.

β€” #3367 The library_files surface still resolves an unscoped document's audience from a single anchor βœ… CLOSED 2026-08-18 β†’ PR #3408 Enterprise

βœ… Closed 2026-08-18 β€” PR #3408: one shared carrier-scope resolution for every document-serving surface, replacing the per-surface single-anchor COALESCE. The two-surfaces-disagree window opened by PR #3366 lasted one day.

The row as it read while open: library_files_mat.scope was still COALESCE(document scope, source_message_id message scope) with seven live RPCs gating on it, so a document correctly withheld in the Archive was still disclosed through Files, Needs and Memories.

β€” #3368 The Python AMA/Ask disclose lane resolves an unscoped document's audience from a single anchor βœ… CLOSED 2026-08-18 β†’ PR #3408 Enterprise

βœ… Closed 2026-08-18 β€” the same PR #3408 shared resolution; the Ask lane's node_provenance_scope_repository projection no longer decides from the uuid anchor. πŸ”‘ The CHOKE-POINT DIRECTIVE's Nth-place test paying out: one question β€” whose audience is this document? β€” answered in SQL, a second SQL surface, and Python, and the fix was finally ONE implementation, not three.

p2 #3284 Three of the four connection matchers are ungated on the audience axis β€” and nodes_v2 RLS has no audience predicate to fall back on βœ… CLOSED 2026-08-16 (PR #3339) Latent

βœ… Closed 2026-08-16 β€” the remaining three matchers gated on the audience axis (PR #3339).

Three of four connection matchers read node text with no audience gate, and nodes_v2 RLS has no audience predicate to fall back on β€” the "RLS covers it" defence does not hold, because RLS gates tenant and tier, never audience. problem_solution.py and duplicate_work.py read node text with no as_user and no audience parameter. HTTP-reachable by any authenticated member, with no shipped UI caller. The #3141 fix closed the semantic-connections matcher; these three remain.

p1 #2824 business_context ships to the client ungated by disclosure level βœ… CLOSED 2026-08-17 (PR #3399) Latent

βœ… Closed 2026-08-17 β€” business_context is now FULL-only via _business_context_at_level (PR #3399), the only always-gated field in the projection; two panel rounds, mutation-verified. The typed-caption class its panel exposed is #3391. The description below is the row as it read while open.

business_context β€” document_purpose, contains_pii, contains_financial_data and a 300-char summary β€” ships on the SSE wire outside the disclosure gate that shapes caption. Nothing renders it today, so the next UI inherits the leak; it is an over-broad payload rather than a rendered leak. The population changed in #2822, which now emits Office documents β€” the highest-signal class.

Leaks through a model call
p0 #3135 The batched scoring prompt shows the LLM every retrieved candidate ungated; only the winners are gated at render βœ… CLOSED 2026-08-16 (PR #3338) Enterprise

βœ… Closed 2026-08-16 β€” reasoning candidates are gated before the scoring prompt reads them (PR #3338), which is the structural gate this row said the soft prompt instruction was standing in for. πŸ›‘ The census it named as never run β€” how often a withheld candidate co-occurred with a rendered winner β€” was not run by the fix either.

The scoring prompt shows the model every retrieved candidate ungated (up to 400 chars each, unredacted); only the winners are gated at render, so a losing restricted candidate still shapes the rationale. The only guard between the two is a soft prompt instruction, not a structural gate. The remaining half after PR #3134 gated the profile; the shared base.py::reason() pipeline means all five reasoning primitives route the same way. Gating before scoring means building the disclosure map earlier β€” one extra evaluation batch per call; current_returned_count semantics need care (candidates fed to the model but never rendered are not "returned", and that number is durable SOC 2 evidence). Not measured: no census of how often a withheld candidate co-occurred with a rendered winner.

p1 #3117 FindBridges feeds ungated profile content into the LLM that writes the rendered rationale βœ… CLOSED 2026-08-18 Enterprise
βœ… Closed 2026-08-18 (this page's own close-out ledger; no single PR names it β€” it was swept with the #3135 pre-scoring gate batch). The row as it read while open: the rendered rationale was written by an LLM shown ~40 ungated profile nodes β€” default_traversal_edges() did not exclude ASKED/ANSWERED, so full-verbatim QUESTION/ANSWER text was reachable. PR #3134 gated the profile half; the retrieved-candidate half was #3135.
The rule, not an instance
p1 #3141 No choke-point for 'a serving traversal's rows must clear an audience check' β€” 10 call sites, 2 confirmed holes βœ… CLOSED 2026-08-16 (PR #3337) Dev only

βœ… Closed 2026-08-16 β€” this row's own recommendation (the AST hook first) shipped as ADR-146 Option B (PR #3337): the build now fails when a serving-traversal call's enclosing function names no disclosure symbol, with genuine layer-up gates recorded as named noqas β€” 15 of the 24 sites at close. ADR-146 records the repository-boundary gate as the endgame the hook buys time for.

The rule has no choke-point — 10 call sites, one rule; five of them are gated only because a human remembered. PR #3290 closed the semantic-connections hole; the rule itself is unresolved. Measured: get_open_asks reaches 7,531 of 7,971 restrictive ask rows; the bridge pool draws on 41,490 of 74,660 restricted person→node pairs. Options: gate at the repository boundary (as_user=True carries the gate), a check.py AST hook, or a typed carrier. Recommendation: the AST hook first, the repository-boundary gate as the real fix.

p1 #3118 ADR needed: an ANSWER node's own text is permanently ungateable under disclose()'s NULL-provenance model βœ… CLOSED 2026-08-17 (PR #3362) Dev only

βœ… Closed 2026-08-17 β€” PR #3362 carries the question's carrier on ANSWER and need-response OFFER nodes, so disclose() finally has provenance to rule on. πŸ›‘ MERGED IS NOT LANDED. The materializer heals going forward only; the 2,023 existing NULL-carrier ANSWER/OFFER nodes stay ungateable until a one-time backfill runs β€” backend/scripts/backfill_answer_offer_carriers.py, merged with the PR, dry-run by default, --apply writes. It reconstructs the carrier from the spec tables, skips merged nodes loudly, and is keyset-chunked and idempotent. It has not been run and no owner is assigned to the run. 🩸 PR #3362's own body says this issue "carries state:not-in-effect until the backfill has run and its result is measured" β€” the label was never applied. Added 2026-08-17. It comes off when the rows are measured as repaired, not when the script is run.

The row as it read while open: all 2,023 answer nodes have source_message_id NULL β€” AnswerProperties has no scope field and the sole writer calls materialize_node without it, so every projection is COALESCE(NULL, NULL). ANSWER text β€” full verbatim conversation text β€” always takes disclose()'s structural passthrough. The ADR must decide three things: carry provenance (materializer change + backfill), or exclude verbatim-content types from the passthrough and fail closed, and what happens to the existing 2,023 rows with no recoverable audience. #3128 blocks the obvious fix.

Over-suppression Β· discharged
p2 #3285 disclose_current_viewer() is SECURITY INVOKER, so a viewer whose own job_function node sits above their tier resolves to no audience identity and is shown nothing βœ… CLOSED 2026-08-16 (PR #3341) Enterprise

βœ… Closed 2026-08-16 β€” the narrower of the two rulings it offered: disclose_current_viewer() resolves SECURITY DEFINER, tenant-guarded (PR #3341), matching its own dependency current_viewer_person_id(). The row as it read while open is below.

disclose_current_viewer() is SECURITY INVOKER, so 12 Regina edges resolve to no audience identity and an entitled viewer is shown nothing β€” over-suppression, not a leak. 2,106 role edges, 12 above tier 0; job_function nodes sit 73 at tier 0, 3 at tier 1, 7 at tier 3 (the tier-3 materializer default is not what lands in practice), and project nodes are far more skewed β€” 117 of 260 at tier 3. Two rulings offered; the narrower is SECURITY DEFINER, matching its own dependency current_viewer_person_id().

p2 #3295 AMA-DISCLOSE became the largest disclosure queue in the repo without anyone deciding it is a track β€” it has no status page this page discharges the ruling Dev only
The track became the largest disclosure queue in the repo with no ruling it was a track; it asked for a ruling, a page, and a decision on the orphaned p0s. This page discharges that ruling β€” the owner ruled 2026-08-16 by ordering pages for all tracks. The orphaned p0 it named (#3155) now sits on this track. The issue stays open, its needs:ruling label still attached.

Why is it in this order?

Why is it in this order?

The criterion, top to bottom β€” each rung breaks only the ties the one above leaves.

  1. Live disclosure leak, reachable today. Content a viewer can be served or shown now that they may not read. The owner ruled the privacy boundary is the judge's output, not its input (2026-08-14), so a rendered leak outranks a prompt leak. Client-reachable outranks HTTP-only; both outrank content that ships but nothing renders.
  2. Leak through a model call. Content the model reads ungated and then writes into rendered output β€” below rung 1 (the output is the boundary), above suppression (the contamination survives into what is rendered).
  3. The rule, not an instance. The choke-point (#3141) and the owed ADR (#3118) each closed a class β€” 2026-08-16 and 2026-08-17 respectively β€” so this rung currently holds no open row; it stands for the next rule-shaped item.

Deliberately ignored: issue age and filing order Β· the priority label alone (severity, re-ranked here on reachability and leak direction) Β· issue number Β· how cheap or well-understood a fix looks Β· track-sharing (#3283 and #3284 sit on WIRE-TRUTH, #3325 on ATTACHMENT-TRUTH β€” sharing does not move an item here).

What the audit found β€” generated

⚠︎ The prose above cites 25 issues that are no longer open

#3595, #3386, #3582, #3581, #3389, #3466, #3465, #3390, #3488, #3283, #3155, #3391, #3128, #2905, #3325, #3367, #3368, #3284, #2824, #3135, #3117, #3141, #3118, #3285, #3295. A closed issue named as current work is the drift this section exists to surface β€” re-derive the prose, or move the reference into History.

Evidence below is generated from the read-only audit of 2026-08-29 and reconciled against the open issues at build time. Every claim cites the issue it came from. Nothing here is a decision β€” rulings stay in the prose above, which is written by hand.

Not yet assessed β€” filed after the audit
  • #4652 [L1-g2-w10] [ama-disclose] enterprise messages of 10 characters or fewer are hidden…
  • #5339 [L56-sm-w3] Link disclosure audit rows to the Ask turn that caused them (turn_id on…
  • #5341 Policy question: should Ask show birth date and marital status from HR sheets to a tier-3…
  • #6163 [disclose] Attachments of hidden email stay reachable by Ask; the spam/sender-verdict…

15 assessed issues closed since the audit and are not shown β€” a closed issue's evidence is history, and printing it as current is how a page starts lying.

Standing rulings

History / forensics

What already merged
  • The 2026-08-26 batch, in merge order. The blind-viewer-job-function invariant detector (PR #3565 β€” flags a bound viewer with no HAS_JOB_FUNCTION edge; the detector class, not a fix) Β· the confidence-floor SQL choke-point (PR #3583 β€” collapses the disclose confidence floor to ONE SQL constant plus a parity detector, closing the Python/SQL divergence class) Β· viewer_user_id on disclosure audit rows (PR #3578 β€” audits previously recorded no viewer) Β· the org-membership classification (PR #3584 β€” data-only, four adversarial-panel rounds recorded in its body; effect deferred to #3595/#3596). None closed an issue.
  • The 2026-08-19β†’25 wave, in merge order. The trait normalisation + CHECK (PR #3475, closing #3466 with state:not-in-effect) Β· the carrierless-LABELED read-side fail-closed (PR #3476, closing #3389) Β· the disclose-gate inlining perf lever (PR #3499 β€” the Archive-timeout work, tracked on #3496) Β· the Kareem repair + reassign_message_sender_person (PR #3535, production-verified) Β· full-corroboration suppression (PR #3548, #3534 Stage A) Β· the #3128 nulling script (PR #3560 β€” dry-run only in the PR; applied as an ops write 2026-08-25) Β· the shadow wiring of the #3386 gate (PR #3568) Β· the write-side stop (PR #3569, #3488) Β· the personal-mode audience bypass (PR #3574 β€” every Mizo Archive was returning completely empty, verified live) Β· the executive audience grant (PR #3562 β€” Regina's CEO could read 26.7% of his own company's messages, designed to reach ~91.9%; its 37,605-row backfill ran owner-executed on 08-25 alongside the HAS_JOB_FUNCTION edge backfill that ended blind viewers) Β· the CI unwedge (PR #3580) Β· the address-to-label family (PR #3511 source pills, PR #3520, PR #3522 facet chips β€” adjacent, no raw mailbox addresses in the UI).
  • PR #3134 (merged 2026-08-07) gated the FindBridges profile before it reaches the LLM prompt β€” the profile half of #3117/#3135.
  • PR #3290 (merged 2026-08-14) gated the semantic-connections serving reads on the audience axis β€” one matcher of the four in #3141/#3284.
  • Issue #2906 (closed) β€” the Q&A pairing splice; it closes the question side, not the answer side (#3118).
The Regina audience census (#3128, #3285)
  • 155 question groups span more than one distinct contributing message; 232 contributing messages' audiences are discarded.
  • 25 pairs are under-gated (ignored carrier strictly tighter); 82 pairs are incomparable β€” every one on the Regina tenant.
  • Over-suppression: 12 Regina role edges point above tier 0, so an entitled viewer resolves to no audience identity and sees nothing.
The NULL-provenance census (#2905, #3118)
  • Content nodes with no message anchor (audience never consulted): answer 100% (1,905/1,905 on 08-01), offer 43.1% (1,311/3,045), question 11.2%, problem 6.8%, need 4.6%, document 0.
  • All 2,023 answer nodes have source_message_id NULL (measured 08-07) β€” full verbatim conversation text that always takes the structural passthrough.