From 3487b635f23166a964ac3ee69d5d19d12ddba055 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 6 Sep 2026 20:54:42 +0900 Subject: [PATCH 1/6] fix(api): contain successful response decoding failures --- docs/adr/0123-provider-error-boundary.md | 5 +++ .../unreadable-response-desktop-20260906.png | Bin 0 -> 13702 bytes .../unreadable-response-mobile-20260906.png | Bin 0 -> 9159 bytes docs/storybook-inventory.md | 2 +- frontend/src/api.test.ts | 34 ++++++++++++++++++ frontend/src/api.ts | 10 +++++- .../src/components/StatusNotice.stories.tsx | 14 ++++++++ 7 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 docs/screenshots/unreadable-response-desktop-20260906.png create mode 100644 docs/screenshots/unreadable-response-mobile-20260906.png diff --git a/docs/adr/0123-provider-error-boundary.md b/docs/adr/0123-provider-error-boundary.md index 48610ebeb..d6c8ca5ee 100644 --- a/docs/adr/0123-provider-error-boundary.md +++ b/docs/adr/0123-provider-error-boundary.md @@ -28,6 +28,11 @@ discarded, and transport failures become a stable status-0 client error before any UI handler can render them. Client-error details remain available only for actionable validation or authorization responses. +Successful HTTP headers do not make the response body trustworthy. Failed +body reads and JSON decoding return the same safe product error, retaining +the observed HTTP status without retaining the raw parser exception or body. +The client does not retry a write whose response could not be decoded. + Missing or malformed evidence remains unavailable; it is never converted into a fabricated negative result. Existing input-validation errors outside a provider boundary retain their client-actionable 422 detail. diff --git a/docs/screenshots/unreadable-response-desktop-20260906.png b/docs/screenshots/unreadable-response-desktop-20260906.png new file mode 100644 index 0000000000000000000000000000000000000000..b8ebf3ffa6d122d1573cf3f72d1059b8d37a4713 GIT binary patch literal 13702 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 literal 0 HcmV?d00001 diff --git a/docs/screenshots/unreadable-response-mobile-20260906.png b/docs/screenshots/unreadable-response-mobile-20260906.png new file mode 100644 index 0000000000000000000000000000000000000000..9fc80ef8f58da813ad1c81ea81342819fec00674 GIT binary patch literal 9159 zcmeHtSx}Sdx~@vMv?8KQ5gP<_q0$b33K*t9yAUuUAR;0HiHe9}iVz@>K%%Wew+JW* zNEo6*f%I7@zW0Bg z=X<9=E_%9YZ`I$rVZ#P((7CghHf-3KvSEY9g`YO5JtNzTKpQsf*Z?|v=5j*e0&7dc zhi-6E-t?k?RUFWHZ~zybM!D6scPg(0C^}1Gd9nO8fLPGo31qv zUkVSZ7{*7v`9-(p-u~ZyU#Ju8*uMqz7Iw-v7(YIrQ!3-I3-;N3XP#iNp_jKAUW@r@O3rXcl({ z+C2(}^E<-nUtw)#78a2DFpr6*$@Y~M`Qyg~%5lBV-UFo|e+FSDG4YVuwb+Hh0~Qt} zZvZSXtGlOrxat}s4DMb0kBzHGVf0(>^TV?E#TR*;-|K5T`VLk3ldojnOF8%IPGd+9 zc4%HduF~~j`5ngWhsm43P5Qr(UcatM2pH(-)24zt5yiZ#Y&Dj3)i>$tdPs%4LJr||I4cbAJa9u2TaMtivnW1g2o4(c z`Mb|yC*}0de&7LO&0Y2&6n>a0u07_63*xti^g!evjWhRJ$ISV72{0FNH_Fn@BS!1q z-syoDC&sQ$9wO@)SS^ScSS#qIDGIhZ$`^c1+Kn^vZ@E73%rvut{do*PgrB^g$rP59 z>Zz7~6`XlMNJrHchg{M>-xTHHaxSk^u_&1`PN)&oymIIE8p>D(TGw{Re;>2cW^me!-6!W+(odLHC331|WzYQhvZG@)(c*`GUajBSV=IN! z<;KSq+SS68Mm-J3pHMa+= zfyPNvd@HXUzgiQ?n4K_oDrt{Er57~ZVGyJ%J5Ahr^F?DH0JmoW;lq;8@2zt$n1=JH zeF4qp7G})4@|bOn(L-|$v+;)ZMsXd`IQB@BnM>7JQy2TRb~pWz9qcOVE>P1bX^v~|ZfVUZHqNW&IYr!&P?Ft(kjvk^O$wUt z3{*t6{kEs?Sa%OYbdg`6V}nds90(^hEK@JiBkP(4Y}5Ms|!K1ljK39VU=S3iWYX&R=%XM-7uji1P5t+GYi@M+2Xj( z^jdHp4RUww_g!7wE&ADv z*}xjr4!78xv_pRx59-OS{$A6vlRDhytp+>v%GY*izn>$%{Ebz~<>1;$vjAiHK;L|B zRr_2MuI$Lt1&s)1;NYYJ6746WoKH*B!U8xm+Vg?i-f+fFN4CZ@srR)6>(n1=P+J3< zDxy93Mu(b%;E`ZPdp)U*a39!xxi3SoLYdpizA@S&vm(`l9>#KfLaf@*>sh`s=zOOGUGRRa6`fF~pv#QrK zLxr-Rm*Ljeav-0{F@0@<4Aqn~PTHMksoFM|^pncn2ZxS#bNTd?;mQTCPbNzM$Btj1 zdC%fH?R^MQfsJ+&?69Gjj~T4#)g;~T6i!j!MlN+3^M@rbd!1eZ$A2G6%OzfYo6sNb zQ%sQoc>j{thd9sZeKdcu>^l4L#M0YfF`xd0W7t@>8#Y>}7CC*t+z3{ah~JXWs+(ou zsjYLz^2u-dQ!kp4mmQYVn3md4+oq*Cm2{lGmPp^Grt{td%l8gaPIG@kw7ehPiXN(1 zs8xJ1a|*J9fm>_P>)pNJfp=53K*T2(mpJ7efw6mfW2zgd#7UAb;;b`5iIA2Rk%c^b zF|#Qz*BlLx2cyv>=gpTfEuU8sg^!0LzsG%l_uC$`Fk%>U_O(m>ZR_x1;>fGiSv{*t z_<93Ih_yb(zSN+mYLlV;>BzBIPIVA{Rd;6Q$ZwiP(Q9Z{tFDzwh6NC>kr((ahNxJ< zuOilV5PnP;_$ z{MM?1(}+b@Wy}LWB)B;yB)yqHpPXzsWX5cYycYiO#p#%dUO<~f(EL3ez_^Y0K5UIr zWEk52TS%f;vBetOJpmWOuZlW)tigS2V(Z_x^IskunZvox2 zh^JREhs}eKU%r?Kg+i`pQ7(t*FF2D*FumE+p}F*GaVRvAC16%zp4DKS|Q`tLpnv&Nc;FZ*pUV)c-TCEz532k z7C)bb6131_ke-%thNnjr;}t<|3S=y#UUWWUzA;~ zP>jzwK3%v^&YMa9*^+QSEs7mDSl7^Sz@1=3whS5bF2h|(?h{HfJc3z`J-Hr93pDrJ z)VS}Te40fj zD(;+PRYW**0j>s3&r#o>M@&ir?`(h`G%1LFL6?_X?X?^`hV_PJ9a0ItDL*f*k zI1tHpr>cUH-;y^QgB3f%oYd)Ub&>Led%;MxI=}S_J+`{gp$65gdCPUFffj*wb*ZD* z+CA4{b-qn%lz6LPyNqB8Fx2H;m>zAz*z=Z63r_c8#KoMZC=NxqQI&9V+OY??eGwu% zaV`%dhULg?$=p8d(%kdVVteUSnQErLt*0sPcEPEMS8i&Sz7TsM*I99_Rj8?#T(}E* zmIM3xRJ8dnHx59AG~E> zI9v)k#}{Ri=FCQ3??V3oR7_#+1&Z;M1e0o;&4=QLC4~o&+V}sW1pRu zE_R=9=0vkrkCYdTM80rb{KQ>Uuug_1|C9Ri+oAy_C*E%$MVmIBh-gMD#t@5_lO4+1 z3M5j|kb7=_!UX<^7Z4~?lzsHUC7K%+LLGdAwRzV+19RgS-n{w0bjV<}g@_+UK6(^6 z))e*a14=NzAqAJ{O1rOFHqxtO-aefTSzjsaBIWGKE8Rj6l~94}A_js3e-D7Y0o(Yt z!v)30ZIi;$9{eFZyzD|be7(ugt`HJ;Abq=$Oc2l=O zvr}|=Iha0RGaivSSfXl`3m>?p6IBN?+Yk$12FjjDdq4437*vWq`9=8grx~Ai;wbyI z@6vz$HUe4Cj73;>QZKc~%~O?YsUBD>5DDjm7Nm}_Pzbw4J<7@Yj$Tie&K$coHQgq9 z5U&?fL4X#Mxomhf`EZb@;3+qWKNdUThEw*|;-u3PCZ2)Rn=@4qc}#rNF>)jrK0;zH zFdbKCsE(*lHHA_ANg@B@v(R6+bmApB3*y0g14fz|qU*v80;~&`7r{a+`k$~9cr(?> zvdf%-25vERt&OutFQYAa6?$I|ck^}X;pJD6flz}AiO>?ksz>tZy`FMjF0NRTK3}i9 z*2N|5jhB}9ZN=hG4$L^UA4xx-^28`7ZzT5fkrH;X{2KL@bIN7%&eMU&5<}%OgYoDz zpXnr@!aZ3Nt&eI`mI@%WAR_m@!ZBv{XLX8t-gHHR5x8~YP-0QEg;h9PUMKKFo5y#MXWR?9}<|LX-jUYL1hT$!YW45v?v z{&8NN;w2}7BuWJ%%yRX7S%P!5gDt9si_7m^>e<~2@75t8M1@IC-L{Akd8h6Z zKGO#f(Yf>E#p8-`%$j$-sb{s2?XIr!ELeWVi4p2T*HhIN#aqF{Ikq>aLFCPbE08?M z0A{Vm*~`Y<`ec1u1+Aa{$m+xen5AyWjJou*GhCuvU0_x*D>H$V_^zy+w7gOs;3;1p zC+ID#0%}*#Z*lzdWkUn#J$Y{tb+iWQi!?$F>yzS=1&giw60WzB#z12;`tjoNA)CJO z9x*sexf~rZG;c=E-WN~bVq(#{-^Uh}Remxjo3NppGUdnnF*oHqG-CY`j#bB)fm49I z&pwXWlr6H@F;eHdjV+MG%U|n!v7qu=AP@d7HY`7x3v$l+aK#Lc!S?SA8!xR%Oyht0 zG^8rb97H#Twq6e>f$LQ5DwOCZL`gGD9D zD_D!6w;@2LkfLa>lGa)s!3fHaSRA8Socs8)yF~G)R(~J(TtLBa?)ILvj-+tK$_$Kp z`cOK^4Wb{Xq2N%U`h+IB&|OSw`}>E)mr52mf(LTXC+%^ju3 zm~nH@`@!{ErNZ&p{ukv)#JU2bR53_z_B|=fCWgSMK2wVop{5A&k2l)_KlT^5rC>}( zH7A^9C=m4cQ%`|fZEjGDfeglpP`Zpg%EsCpDLPPg4I`S=^0`SLcT@C);{QnTs2uTB zSM??w%)Z!pW+o7 zA;LYkMzW1q*h2!s8J~Xio8V>ujB(f_J4Ejd1~6;xpShdUiB_iV&NyRggppGVi)KgS zn{E{`v&C?1YWE`73iLcEhNG}A8_kXN7=ytpJIbK@io~?6Q8+!4Dc~I9v#mmkJB~n$ zLvZ~?j14!w)piog?Rf6O6oV@&MX<5~hL|d$zc)5KIrT`7(Z#!A;@M4h1ZMK8{^>!- zPkf#9hvB!BT$uTTezM-Q9eaB-GQHgbgYOGMCQCzEG~!NhTi93j?t^7d{S(lyPkBkr`#IN<&Bf5rY=wq#A=Ps$$<6 zp7{P$6m^BRGPK&@1)EkeI~8M1R_S4b60$llY0>VB<^gbfT(%A)SMn zaKV@$>{X8k6ILgmdCoEnSoc$PDr!%fnA=Dw1?Z=<{VN4}1O`pJ@(MqURO&bK%Gg#o z<)r%H>w%qVjAAUxVSb*@rTNdDGo8eOA_Q-eusMGz)i~qseu)E3dWG z6`|*ze|MX@KcyXQ%1fRbt7Le~GL45;tyYas$9{0uDRWC!Buj>=2Jk#$s0JNtAIpoG z&w+%E)CNOApuPi_774?Rz#*9}|Nci$n;WbZU0%27+2-%1&EnCLtHAhit?}9V>!TFA4@`5IStQ>zUx<7RU zr|4~mS{_BPyg$+}$6{sf8e)yj+82xwxAe>Z|Ie4+oB0z?vyBmT<&(cVE5o{C1&0Ef zW+|Iv@Y$-^qhJMKb9{^3JST#6X{s`cQJRqG@~NNFaXQ_e&h;OK%f_oJl=d6_q0gVQ zN9%CygSno|^uYwaPq7pqH@ud%6Z-iI_xxPKMP_puFvsuWQ|LV_nRqN4(=y$0vT?+x zs-IO1?j7>-P_&&ybRn(eT~+X|hE6&fTzhvw!%4oJ2UEF-)se*58J9;N&I&q($etTl z&h>dBnhm(85P>K~Z743skwH|bUq2AHW(d=GD0%RNaAr$qQSuce$K&IWEJ_ZJd!N=E zVHPnU#|RNm<^!DD=S4xz_H=L)st*uN3n`8|h zccK!@y>-9*bVR>z#bB9qR{zE?b))!i~H-|fu({yLxl}~EBtMb^StLc%-J8`1OSkx$4Z1T?DbHEdXs=+y6p$)23$!_wNAxZT@>ga4PyQ8wUNQlcm28`%h<3 ze~0Jq@chNI+`l39KLRPs?_oF8O9Hq4y%+Ge6#sRr34g=+e+16|dm${>QE%a>RQon1 i=cqq6|C?_b8(cR>E3en4K2v)(fLuJ!R{!hzZ~q5Ure-k! literal 0 HcmV?d00001 diff --git a/docs/storybook-inventory.md b/docs/storybook-inventory.md index f426285a6..ef6d8845b 100644 --- a/docs/storybook-inventory.md +++ b/docs/storybook-inventory.md @@ -19,7 +19,7 @@ operator-facing control you can click before changing product CSS. | `Analysis/LineageEntityPicker` | Choose which corp to reconstruct, then click Request a lineage reconstruction. | `--space-control-gap`, `--size-control-min`, `--radius-control`, `LineageEntityPicker` | | `Admin/AdminPanel` | Change the tenant brand name, then verify the saved or failed state before leaving settings. | `--surface`, `--border`, `--space-panel-block`, `AdminPanel` | | `Lineage/LineageDag` | Open a reconstructed connection to read its inferred channel scores and Allen interval relation, or open the current branch node; compare empty, single-branch, grouped/forked, mobile-scroll, ungrouped, and long-title states before changing graph CSS. On narrow viewports, swipe the named viewport or focus it and use arrow keys to inspect the full lineage. | `--color-accent-background`, `--radius-control`, `--surface`, `--border`, `--color-focus-border`, `--size-control-min`, `LineageDag` | -| `Chrome/StatusNotice` | Read success, unavailable, or retry copy, then take the named next action. Success and unavailable are a named region (not live `role=status`); Retry is `role=alert` and only on the retry kind. Calendar's missing Naruon projection uses unavailable. | `--badge-status-success-*`, `--badge-status-pending-*`, `--badge-status-danger-*`, `StatusNotice` | +| `Chrome/StatusNotice` | Read success, unavailable, or retry copy, then take the named next action. Success and unavailable are a named region (not live `role=status`); Retry is `role=alert` and only on the retry kind. Calendar's missing Naruon projection uses unavailable. `UnreadableResponse` renders the safe failed-response message without an automatic write retry; desktop (1440 px) and mobile (390 px) screenshots are presentation evidence only. | `--badge-status-success-*`, `--badge-status-pending-*`, `--badge-status-danger-*`, `StatusNotice` | | `Chrome/PopupCloseButton` | Close the evidence panel or post popup. | `--space-close-inset`, `--font-size-close`, `PopupCloseButton` | | `Workspace/WorkspaceCalendar` | Read observed Naruon events, or open a commitment to land on that post. Fail-closed copy stays `이 범위의 일정을 아직 받을 수 없습니다`. | `--color-chip-border`, `WorkspaceCalendar`, `EvidenceStatusMark` | | `Ask Agent/Public claim verification` | Compare supported, refuted, and not-enough-information states; open only the external evidence link, then review the separate internal citation before changing governed graph state. | `--space-panel-block`, `--space-control-gap`, `--color-border`, `--size-control-min`, `PublicClaimVerification` | diff --git a/frontend/src/api.test.ts b/frontend/src/api.test.ts index 9495dc241..b40247287 100644 --- a/frontend/src/api.test.ts +++ b/frontend/src/api.test.ts @@ -14,6 +14,40 @@ afterEach(() => { }); describe("backendFetch provider-error boundary", () => { + it.each([200, 201])("hides malformed successful response bodies at HTTP %s", async (status) => { + vi.stubGlobal("fetch", vi.fn().mockImplementation(async () => new Response( + 'synthetic-private-body {"unfinished":', + { status, headers: { "Content-Type": "application/json" } }, + ))); + + for (const request of [ + () => fetchMe("synthetic-token"), + () => updateTenantConfig("synthetic-token", "Example tenant"), + ]) { + const error = await request().catch((reason: unknown) => reason); + expect(error).toBeInstanceOf(BackendError); + expect(error).toMatchObject({ + status, + message: "The service could not complete this request. Try again later.", + }); + expect(String(error)).not.toContain("synthetic-private-body"); + } + }); + + it("hides body-stream failures after successful response headers", async () => { + vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response( + new ReadableStream({ + start(controller) { controller.error(new Error("synthetic-private-stream")); }, + }), + { status: 200 }, + ))); + await expect(fetchMe("synthetic-token")).rejects.toMatchObject({ + name: "BackendError", + status: 200, + message: "The service could not complete this request. Try again later.", + }); + }); + it("binds the selected Dashboard period as inclusive API dates", async () => { const fetchMock = vi.fn().mockResolvedValue( new Response(JSON.stringify({ cases: [] }), { headers: { "Content-Type": "application/json" } }), diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 5db021a05..27d720faf 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -577,7 +577,15 @@ async function backendFetch( } throw new BackendError(path, response.status, detail); } - return response.json() as Promise; + try { + return await response.json() as T; + } catch { + throw new BackendError( + path, + response.status, + "The service could not complete this request. Try again later.", + ); + } } export interface LineageGraphNode { diff --git a/frontend/src/components/StatusNotice.stories.tsx b/frontend/src/components/StatusNotice.stories.tsx index 89ad543b4..1d289fd2b 100644 --- a/frontend/src/components/StatusNotice.stories.tsx +++ b/frontend/src/components/StatusNotice.stories.tsx @@ -63,3 +63,17 @@ export const Retry: Story = { await expect(args.onRetry).toHaveBeenCalledTimes(1); }, }; + +export const UnreadableResponse: Story = { + args: { + kind: "retry", + message: "The service could not complete this request. Try again later.", + }, + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + await expect(canvas.getByRole("alert")).toHaveTextContent( + "The service could not complete this request. Try again later.", + ); + await expect(canvas.queryByRole("button")).toBeNull(); + }, +}; From 149af3c2208a4041773fb88a708cfb7087e56c2c Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 6 Sep 2026 20:57:27 +0900 Subject: [PATCH 2/6] docs: refresh exact-head queue and error-boundary evidence --- docs/product-technical-gap-baseline.md | 73 ++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index b5d31877b..c699711eb 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,5 +1,78 @@ # Product & Technical Gap Baseline +## Current bounded audit — 2026-09-06 + +Protected base: `83eba56149eb802cd63642c507c324c9976ec78e`. Tested implementation: `3487b635f23166a964ac3ee69d5d19d12ddba055`. +Queue observed at 2026-09-06T11:50:20.768799+00:00: 121 open PRs (115 Draft, 6 Ready), 16 open issues. A second complete head inventory found no moved heads. This snapshot supersedes queue and runtime claims in the dated historical material below. + +### Authority and evidence boundaries + +- Read the current LineageWeave PRD, ADR 0123 (error disclosure), and ADR 0220 (existing status notice). The connected contextual-orchestrator authority is `docs/product_planning.md` and its README; there is no `docs/product-requirements.md` at that owner. No orchestration or mathematical policy changes in this slice. +- GitHub metadata confirms canonical `ContextualWisdomLab/LineageWeave`, `RankWeave`, `ThreadWeave`, `TEPP`, `contextual-orchestrator`, and lowercase `ContextualWisdomLab/disksage`. The differently cased DiskSage PRD register entry remains a documentation discrepancy, not a second repository. +- Normative architecture remains in ADRs. Research references in ADR 0123 justify non-disclosure; the Fetch response contract explains separate body-read/JSON failures. Neither source establishes capacity, model quality, weights, population inference, or release completion. +- DeepWiki could not find this repository. Context7 returned quota exhaustion. No inferred documentation or research result substitutes for either unavailable source. + +### Exact-head Ready queue + +Counts below are REST check runs filtered by `head_sha` equality, including optional checks; they are not a claim that the required workflow set passed. All six heads were rechecked unchanged, have zero unresolved threads (complete thread pagination), zero current-head APPROVED reviews, and retain normal squash auto-merge. No merge was performed. + +| PR | Exact head | Success / failure / skipped / cancelled | +|---|---|---| +| #929 | `2a8ed5d02f4a3082b346d923d754c1ff37ebff52` | 29 / 6 / 4 / 0 | +| #914 | `61ed3a3712d252e3c179a71d297c52f05e1bac20` | 25 / 5 / 7 / 0 | +| #911 | `5d40eed35a0b6e0d182397f8d02b29c38e9bdd17` | 28 / 7 / 5 / 0 | +| #907 | `847a15e73e69bfc768d517a83fa8706aecfafe7e` | 22 / 5 / 7 / 2 | +| #802 | `32f1cda10a2a1a6cabd64a3ae6f59bd6f0b20fd6` | 28 / 7 / 4 / 0 | +| #780 | `1d8fa267b059289e77301a09985dfac70a439814` | 8 / 0 / 8 / 11 | + +Ruleset 18156473 requires an independent approval, resolved threads, and seven central workflows; ruleset 21065108 prohibits force pushes. Main deletion/non-fast-forward protections also apply. Existing failure triage identifies central dispatcher and Dependency Review ownership, not a replacement leaf implementation. `.github#1927`, `#1902`, and `#810` remain open; `.github#1932` is independently confirmed merged at `6f8c51d7389c22ebaf294fe8fe9ef495257883c0`. This owner merge is not LineageWeave release evidence. No in-progress LineageWeave Actions runs were returned by the status-filtered inventory, so no stale run was cancelled. + +### Cross-PR collision audit + +All 121 PR file inventories were fetched. Distinct ADR filenames share these identifiers: + +- 0233: `docs/adr/0233-global-ask-semantic-candidate-nomination.md`; `docs/adr/0233-leftover-map-unexplained-share.md` +- 0245: `docs/adr/0245-io-occupational-taxonomy-in-the-published-ontology.md`; `docs/adr/0245-lineage-scoring-and-entity-resolution-owner-contract.md` +- 0272: `docs/adr/0272-leftover-map-segment-reconstruction.md`; `docs/adr/0272-twenty-millisecond-read-slo.md` +- 0279: `docs/adr/0279-global-ask-exact-semantic-index.md`; `docs/adr/0279-leftover-map-segment-expected.md` +- 0289: `docs/adr/0289-leftover-map-compare-coverage.md`; `docs/adr/0289-leftover-map-plot-singular.md` +- 0290: `docs/adr/0290-leftover-map-axis-singular.md`; `docs/adr/0290-leftover-map-compare-item-coverage.md` +- 0300: `docs/adr/0300-contextual-orchestrator-owner-boundary.md`; `docs/adr/0300-leftover-map-compare-expected.md` +- 0301: `docs/adr/0301-dichotomous-measurement-policy.md`; `docs/adr/0301-leftover-map-compare-rank.md` +- 0305: `docs/adr/0305-leftover-map-compare-plot-axis-share.md`; `docs/adr/0305-leftover-map-compare-rank-payload.md` +- 0335: `docs/adr/0335-leftover-map-compare-plot-tick-origin-badge.md`; `docs/adr/0335-leftover-map-plot-criterion-coordinates.md` +- 0355: `docs/adr/0355-dynamic-evaluation-lineage.md`; `docs/adr/0355-leftover-map-plot-origin-badge.md` + +Release labels also collide in PR titles (supporting metadata, not verified manifest versions): v2.92.0: #877/#876; v2.62.0: #844/#843; v2.61.0: #842/#841; v2.50.0: #828/#826; v2.47.0: #823/#822; v2.46.0: #821/#820. Six PR inventories touch migrations. Semantic migration compatibility and the actual release manifest values remain to be checked before each protected merge; filename/title collision detection is not sufficient clearance. + +Nine open PRs touch `frontend/src/api.ts`. Eight retain the uncontained successful-body decoder. #909 moves that decoder into `apiTransport.ts` and still rethrows parser exceptions, while adding an abort/deadline boundary. When integrating #909, carry this sanitization into the moved decoder and preserve its abort behavior; do not overwrite its request gate or hierarchy repair. This slice changes no route, response schema, migration, release number, or new ADR identifier. + +Keep #780 → #934/#936/#937, #934 → #935, #929 → #932, and the report/owner stacks parent-first. #959 owns ontology authorization-denial recovery; its concurrent repair is not duplicated here. No child was retargeted before a protected parent merge. + +### Highest-priority uncovered repair in this sweep + +The shared browser client allowed successful HTTP response parsing errors to escape into customer error handlers. A malformed body can place source content inside a native SyntaxError; a failed response stream can expose its exception message. This affects both read paths and settings writes. It is a directly reproduced privacy/error-recovery gap, selected ahead of adding new product surface while the existing Voice, localization, and authorization lanes remain in review. No numerical priority model or population claim is used. + +The minimal repair contains body-read/JSON failures at the shared client boundary, retains the observed HTTP status, and returns the existing safe next-action message. It neither supplies replacement evidence nor retries a write. Regression evidence: three new cases failed before the repair; all 10 API tests pass after it, covering HTTP 200/201 reads and writes and an errored native response stream. Six existing StatusNotice tests pass. TypeScript, oxlint, and the production build pass with the installed project toolchain; the build retains its existing large-chunk warning. Frozen-lock hosted checks are separate and not claimed here. + +Existing `Chrome/StatusNotice/UnreadableResponse` renders the safe copy with existing semantic tokens and alert semantics. Desktop 1440×900 and mobile 390×844 screenshots were inspected: the message is visible with no document overflow. These are synthetic presentation evidence, not authenticated deployed UI acceptance: + +- [Desktop](screenshots/unreadable-response-desktop-20260906.png) +- [Mobile](screenshots/unreadable-response-mobile-20260906.png) + +### Current runtime and remaining acceptance + +- Official Compose project `lineageweave` is running. Read-only PostgreSQL aggregate: 43,189 source posts; the Voice-association table exists. No source name, record key, body, title, or credential was emitted. This is a descriptive whole-store count, not population inference or a claim that a diagnostic sample is representative. +- The seeded synthetic account authenticated and `/api/posts` returned a JSON collection on the running PostgreSQL-backed API. This proves only that read path; the runtime is not the candidate commit. No current authenticated Voice truth/cutoff/PROV-O/write/export acceptance or candidate rendered-product acceptance is marked complete. +- The shared running store is not certified synthetic-only. The existing k6 harness queries unrestricted posts/lineage and submits Ask, so it was not launched against this store. Synthetic-only authenticated end-to-end concurrency, latency, error rate, throughput, and PostgreSQL/worker/Valkey/gateway saturation remain unavailable. No speculative bottleneck repair, saturation claim, SLO, or population estimator was added. +- Twelve atomic Voices and evidence-bearing extensible associations remain the contract. Preserve carrying Post versus derivation evidence, hidden-evidence omission, truth status, PROV-O, cutoff, and paged multi-Voice union in the respective owner PRs. Historical screenshots or a present table do not satisfy those acceptance conditions. +- No temporary containers were created and no data volume was removed. Protected integration, independent approval, full required checks, and merge SHA remain outstanding for this candidate. + +## Historical supporting snapshots + +Everything below is dated supporting evidence. It is not a current queue inventory, current runtime confirmation, or release acceptance for the implementation above. + + > Exact-head loop overlay: 2026-08-29 13:20 KST. Protected `main` is > `fc13acaa20adca11968238e398d4aafcf62b6cee` (v2.23.0 leftover-map > explained leftover share, #775). Open ready PRs still lack independent From 4f86ccc1a7885c55340adecff99bd273bbfe8bed Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 6 Sep 2026 22:16:28 +0900 Subject: [PATCH 3/6] fix(api): keep request identifiers out of fallback errors --- docs/adr/0123-provider-error-boundary.md | 3 +++ frontend/src/api.test.ts | 29 ++++++++++++++++++++++++ frontend/src/api.ts | 4 ++-- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/docs/adr/0123-provider-error-boundary.md b/docs/adr/0123-provider-error-boundary.md index d6c8ca5ee..28878031b 100644 --- a/docs/adr/0123-provider-error-boundary.md +++ b/docs/adr/0123-provider-error-boundary.md @@ -27,6 +27,9 @@ The browser API client is a second trust boundary: HTTP 5xx details are discarded, and transport failures become a stable status-0 client error before any UI handler can render them. Client-error details remain available only for actionable validation or authorization responses. +If no actionable detail is available, the browser shows the existing safe +next-action message, never the request URL or its record identifiers. The +observed HTTP status remains available to authorization/recovery handlers. Successful HTTP headers do not make the response body trustworthy. Failed body reads and JSON decoding return the same safe product error, retaining diff --git a/frontend/src/api.test.ts b/frontend/src/api.test.ts index b40247287..abd6543b6 100644 --- a/frontend/src/api.test.ts +++ b/frontend/src/api.test.ts @@ -14,6 +14,35 @@ afterEach(() => { }); describe("backendFetch provider-error boundary", () => { + it.each([400, 401, 403, 404, 422])("keeps request identifiers out of an unreadable HTTP %s error", async (status) => { + vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response("not JSON", { status }))); + + const error = await fetchOccupationRatings("synthetic-token", { + onetsocCode: "synthetic-private-occupation", + dataReleaseCode: "synthetic-private-release", + sourceTableCode: "synthetic-private-source", + }).catch((reason: unknown) => reason); + + expect(error).toBeInstanceOf(BackendError); + expect(error).toMatchObject({ + status, + message: "The service could not complete this request. Try again later.", + }); + expect(String(error)).not.toContain("synthetic-private"); + expect(String(error)).not.toContain("/api/"); + }); + + it("preserves actionable validation details", async () => { + vi.stubGlobal("fetch", vi.fn().mockResolvedValue(new Response( + JSON.stringify({ detail: "Choose an available source and try again." }), + { status: 422 }, + ))); + await expect(fetchMe("synthetic-token")).rejects.toMatchObject({ + status: 422, + message: "Choose an available source and try again.", + }); + }); + it.each([200, 201])("hides malformed successful response bodies at HTTP %s", async (status) => { vi.stubGlobal("fetch", vi.fn().mockImplementation(async () => new Response( 'synthetic-private-body {"unfinished":', diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 27d720faf..263a1b5a5 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -518,7 +518,7 @@ export interface ActivityEvent { export class BackendError extends Error { readonly status: number; - constructor(path: string, status: number, detail?: string) { + constructor(_path: string, status: number, detail?: string) { const message = status === 0 ? "The service is unreachable. Try again later." @@ -526,7 +526,7 @@ export class BackendError extends Error { ? "The service could not complete this request. Try again later." : detail && detail.trim() ? detail - : `${path} -> HTTP ${status}`; + : "The service could not complete this request. Try again later."; super(message); this.name = "BackendError"; this.status = status; From 38900637e646f8a6d29438406202e1e67cfc0417 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 6 Sep 2026 22:18:34 +0900 Subject: [PATCH 4/6] docs: link current repair evidence to reviewed PR head --- docs/product-technical-gap-baseline.md | 40 +++++++++++++++++++------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index c699711eb..450b7f0c0 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -2,12 +2,17 @@ ## Current bounded audit — 2026-09-06 -Protected base: `83eba56149eb802cd63642c507c324c9976ec78e`. Tested implementation: `3487b635f23166a964ac3ee69d5d19d12ddba055`. -Queue observed at 2026-09-06T11:50:20.768799+00:00: 121 open PRs (115 Draft, 6 Ready), 16 open issues. A second complete head inventory found no moved heads. This snapshot supersedes queue and runtime claims in the dated historical material below. +Protected base: `83eba56149eb802cd63642c507c324c9976ec78e`. Tested implementation: `4f86ccc1a7885c55340adecff99bd273bbfe8bed`. +The previously reviewed documentation head `149af3c2208a4041773fb88a708cfb7087e56c2c` +is the direct child of tested implementation `3487b635f23166a964ac3ee69d5d19d12ddba055`, +which is the direct child of the protected base. The new tested implementation +is the direct child of that documentation head. This document commit follows +the tested implementation; it does not transfer hosted Checks between heads. +Queue rechecked on 2026-09-06 at 13:14 UTC: 122 open PRs (116 Draft, 6 Ready), 16 open issues. A second complete head inventory found no moved heads. This snapshot supersedes queue and runtime claims in the dated historical material below. ### Authority and evidence boundaries -- Read the current LineageWeave PRD, ADR 0123 (error disclosure), and ADR 0220 (existing status notice). The connected contextual-orchestrator authority is `docs/product_planning.md` and its README; there is no `docs/product-requirements.md` at that owner. No orchestration or mathematical policy changes in this slice. +- Read the current LineageWeave PRD, ADR 0123 (error disclosure), and ADR 0220 (existing status notice). The connected contextual-orchestrator authority is `docs/product_planning.md` and `docs/architecture.md`; there is no `docs/product-requirements.md` at that owner. No orchestration or mathematical policy changes in this slice. - GitHub metadata confirms canonical `ContextualWisdomLab/LineageWeave`, `RankWeave`, `ThreadWeave`, `TEPP`, `contextual-orchestrator`, and lowercase `ContextualWisdomLab/disksage`. The differently cased DiskSage PRD register entry remains a documentation discrepancy, not a second repository. - Normative architecture remains in ADRs. Research references in ADR 0123 justify non-disclosure; the Fetch response contract explains separate body-read/JSON failures. Neither source establishes capacity, model quality, weights, population inference, or release completion. - DeepWiki could not find this repository. Context7 returned quota exhaustion. No inferred documentation or research result substitutes for either unavailable source. @@ -25,11 +30,13 @@ Counts below are REST check runs filtered by `head_sha` equality, including opti | #802 | `32f1cda10a2a1a6cabd64a3ae6f59bd6f0b20fd6` | 28 / 7 / 4 / 0 | | #780 | `1d8fa267b059289e77301a09985dfac70a439814` | 8 / 0 / 8 / 11 | -Ruleset 18156473 requires an independent approval, resolved threads, and seven central workflows; ruleset 21065108 prohibits force pushes. Main deletion/non-fast-forward protections also apply. Existing failure triage identifies central dispatcher and Dependency Review ownership, not a replacement leaf implementation. `.github#1927`, `#1902`, and `#810` remain open; `.github#1932` is independently confirmed merged at `6f8c51d7389c22ebaf294fe8fe9ef495257883c0`. This owner merge is not LineageWeave release evidence. No in-progress LineageWeave Actions runs were returned by the status-filtered inventory, so no stale run was cancelled. +Ruleset 18156473 requires an independent approval, resolved threads, and seven central workflows; ruleset 21065108 prohibits force pushes. Main deletion/non-fast-forward protections also apply. Existing failure triage identifies central dispatcher and Dependency Review ownership, not a replacement leaf implementation. `.github#1927`, `#1902`, and `#810` remain open; `.github#1932` is independently confirmed merged at `6f8c51d7389c22ebaf294fe8fe9ef495257883c0`. This owner merge is not LineageWeave release evidence. The fresh status-filtered inventory again returned no in-progress LineageWeave Actions runs; no current-main or open-PR run was cancelled. In the earlier inventory, no in-progress LineageWeave Actions runs were returned by the status-filtered inventory, so no stale run was cancelled. ### Cross-PR collision audit -All 121 PR file inventories were fetched. Distinct ADR filenames share these identifiers: +All 122 PR file inventories were fetched again and all 122 heads were rechecked unchanged. Complete review-thread pagination found 103 unresolved threads on 50 PRs, including informational observations and execution requests; this count is not 103 confirmed defects. The six Ready lanes and the Voice repair stack have no unresolved threads. #960 has one valid documentation-lineage clarification, addressed above. Distinct changes must be verified in the owning stack before resolving other threads. #811 has a concrete lower-viewport rank-caption clipping report; the other three threads there are execution or informational notes, not three more implementation defects. + + Distinct ADR filenames share these identifiers: - 0233: `docs/adr/0233-global-ask-semantic-candidate-nomination.md`; `docs/adr/0233-leftover-map-unexplained-share.md` - 0245: `docs/adr/0245-io-occupational-taxonomy-in-the-published-ontology.md`; `docs/adr/0245-lineage-scoring-and-entity-resolution-owner-contract.md` @@ -45,7 +52,7 @@ All 121 PR file inventories were fetched. Distinct ADR filenames share these ide Release labels also collide in PR titles (supporting metadata, not verified manifest versions): v2.92.0: #877/#876; v2.62.0: #844/#843; v2.61.0: #842/#841; v2.50.0: #828/#826; v2.47.0: #823/#822; v2.46.0: #821/#820. Six PR inventories touch migrations. Semantic migration compatibility and the actual release manifest values remain to be checked before each protected merge; filename/title collision detection is not sufficient clearance. -Nine open PRs touch `frontend/src/api.ts`. Eight retain the uncontained successful-body decoder. #909 moves that decoder into `apiTransport.ts` and still rethrows parser exceptions, while adding an abort/deadline boundary. When integrating #909, carry this sanitization into the moved decoder and preserve its abort behavior; do not overwrite its request gate or hierarchy repair. This slice changes no route, response schema, migration, release number, or new ADR identifier. +Ten open PRs touch `frontend/src/api.ts`, including this candidate. Eight retain the uncontained successful-body decoder. #909 moves that decoder into `apiTransport.ts` and still rethrows parser exceptions, while adding an abort/deadline boundary. When integrating #909, carry this sanitization into the moved decoder and preserve its abort behavior; do not overwrite its request gate or hierarchy repair. This slice changes no route, response schema, migration, release number, or new ADR identifier. Keep #780 → #934/#936/#937, #934 → #935, #929 → #932, and the report/owner stacks parent-first. #959 owns ontology authorization-denial recovery; its concurrent repair is not duplicated here. No child was retargeted before a protected parent merge. @@ -53,17 +60,30 @@ Keep #780 → #934/#936/#937, #934 → #935, #929 → #932, and the report/owner The shared browser client allowed successful HTTP response parsing errors to escape into customer error handlers. A malformed body can place source content inside a native SyntaxError; a failed response stream can expose its exception message. This affects both read paths and settings writes. It is a directly reproduced privacy/error-recovery gap, selected ahead of adding new product surface while the existing Voice, localization, and authorization lanes remain in review. No numerical priority model or population claim is used. -The minimal repair contains body-read/JSON failures at the shared client boundary, retains the observed HTTP status, and returns the existing safe next-action message. It neither supplies replacement evidence nor retries a write. Regression evidence: three new cases failed before the repair; all 10 API tests pass after it, covering HTTP 200/201 reads and writes and an errored native response stream. Six existing StatusNotice tests pass. TypeScript, oxlint, and the production build pass with the installed project toolchain; the build retains its existing large-chunk warning. Frozen-lock hosted checks are separate and not claimed here. +The initial repair contains body-read/JSON failures at the shared client boundary, retains the observed HTTP status, and returns the existing safe next-action message. It neither supplies replacement evidence nor retries a write. Regression evidence: three new cases failed before the repair; all 10 API tests pass after it, covering HTTP 200/201 reads and writes and an errored native response stream. Six existing StatusNotice tests pass. TypeScript, oxlint, and the production build pass with the installed project toolchain; the build retains its existing large-chunk warning. Frozen-lock hosted checks are separate and not claimed here. + +The follow-up closes another reproduced path in the same boundary: unreadable +4xx responses used to include the request URL and its identifiers in the visible +error. The fallback now uses the existing safe message. HTTP status still reaches +authorization handlers, and actionable validation details remain unchanged. +The installed Node 24.19.0 toolchain passed all 16 API tests (five request-identity +cases and one validation-detail case added), TypeScript, oxlint, the production +build, and five documentation-hygiene tests. The build retains its existing +large-chunk warning. Initial Vitest worker starts timed out; the successful API +run used one thread. The unchanged StatusNotice suite hit the same worker-start +timeout on this follow-up and is not counted as a fresh pass. pnpm's automatic +install refused the pre-existing shared node_modules symlink; no shared dependencies +were deleted or changed. Frozen-lock hosted verification remains outstanding. -Existing `Chrome/StatusNotice/UnreadableResponse` renders the safe copy with existing semantic tokens and alert semantics. Desktop 1440×900 and mobile 390×844 screenshots were inspected: the message is visible with no document overflow. These are synthetic presentation evidence, not authenticated deployed UI acceptance: +Existing `Chrome/StatusNotice/UnreadableResponse` renders the safe copy with existing semantic tokens and alert semantics. Desktop 1440×900 and mobile 390×844 screenshots were recaptured and inspected on this follow-up: the message is visible with no document overflow. These are synthetic presentation evidence, not authenticated deployed UI acceptance: - [Desktop](screenshots/unreadable-response-desktop-20260906.png) - [Mobile](screenshots/unreadable-response-mobile-20260906.png) ### Current runtime and remaining acceptance -- Official Compose project `lineageweave` is running. Read-only PostgreSQL aggregate: 43,189 source posts; the Voice-association table exists. No source name, record key, body, title, or credential was emitted. This is a descriptive whole-store count, not population inference or a claim that a diagnostic sample is representative. -- The seeded synthetic account authenticated and `/api/posts` returned a JSON collection on the running PostgreSQL-backed API. This proves only that read path; the runtime is not the candidate commit. No current authenticated Voice truth/cutoff/PROV-O/write/export acceptance or candidate rendered-product acceptance is marked complete. +- Official Compose project `lineageweave` is running. Read-only PostgreSQL aggregate rechecked during this follow-up: 43,189 source posts; the Voice-association table exists. No source name, record key, body, title, or credential was emitted. This is a descriptive whole-store count, not population inference or a claim that a diagnostic sample is representative. +- In the earlier 11:50 UTC observation, the seeded synthetic account authenticated and `/api/posts` returned a JSON collection on the running PostgreSQL-backed API. This proves only that read path; the runtime is not the candidate commit. No current authenticated Voice truth/cutoff/PROV-O/write/export acceptance or candidate rendered-product acceptance is marked complete. - The shared running store is not certified synthetic-only. The existing k6 harness queries unrestricted posts/lineage and submits Ask, so it was not launched against this store. Synthetic-only authenticated end-to-end concurrency, latency, error rate, throughput, and PostgreSQL/worker/Valkey/gateway saturation remain unavailable. No speculative bottleneck repair, saturation claim, SLO, or population estimator was added. - Twelve atomic Voices and evidence-bearing extensible associations remain the contract. Preserve carrying Post versus derivation evidence, hidden-evidence omission, truth status, PROV-O, cutoff, and paged multi-Voice union in the respective owner PRs. Historical screenshots or a present table do not satisfy those acceptance conditions. - No temporary containers were created and no data volume was removed. Protected integration, independent approval, full required checks, and merge SHA remain outstanding for this candidate. From e173c1b7b18daf4f19ce83ef1397a9aa58fb56b3 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 6 Sep 2026 22:24:52 +0900 Subject: [PATCH 5/6] docs: record rank regression and concurrent draft transition --- docs/product-technical-gap-baseline.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 450b7f0c0..8e51c5a9a 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -34,7 +34,7 @@ Ruleset 18156473 requires an independent approval, resolved threads, and seven c ### Cross-PR collision audit -All 122 PR file inventories were fetched again and all 122 heads were rechecked unchanged. Complete review-thread pagination found 103 unresolved threads on 50 PRs, including informational observations and execution requests; this count is not 103 confirmed defects. The six Ready lanes and the Voice repair stack have no unresolved threads. #960 has one valid documentation-lineage clarification, addressed above. Distinct changes must be verified in the owning stack before resolving other threads. #811 has a concrete lower-viewport rank-caption clipping report; the other three threads there are execution or informational notes, not three more implementation defects. +All 122 PR file inventories were fetched again and all 122 heads were rechecked unchanged. Complete review-thread pagination found 103 unresolved threads on 50 PRs, including informational observations and execution requests; this count is not 103 confirmed defects. The six Ready lanes and the Voice repair stack have no unresolved threads. #960 has one valid documentation-lineage clarification, addressed above. Distinct changes must be verified in the owning stack before resolving other threads. #811 was subsequently rechecked at `e0fad9af9d8fe446769474bd5fc8300c902a052f`: commit `66bccfbc670188ce46e22b0f0ec345942a5e50b6` already contains the caption-bounds repair. Follow-up `4dcd789385f67c76a4e479194f566f35bb5d2d76` preserves that implementation and strengthens the full-caption regression to assert the final rank baseline for both plot edges. Layout, rank, and short-canvas suites pass: 55 tests. The clipping thread is resolved; execution/informational threads are distinct from new confirmed defects. The parent is still #802, so #811 remains Draft on its existing base. Distinct ADR filenames share these identifiers: @@ -80,6 +80,18 @@ Existing `Chrome/StatusNotice/UnreadableResponse` renders the safe copy with exi - [Desktop](screenshots/unreadable-response-desktop-20260906.png) - [Mobile](screenshots/unreadable-response-mobile-20260906.png) +### Concurrent delivery-state change + +#960 was pushed at `38900637e646f8a6d29438406202e1e67cfc0417`, its +verified documentation thread was resolved, and normal Ready/auto-merge was +set at 13:19 UTC. A separate action under the same GitHub account converted it +to Draft at 13:21:20 UTC; GitHub disabled auto-merge because of that conversion. +The timeline records no reason for the Draft decision. This loop does not +continually reverse another actor's state changes. Required workflows remain +queued/cancelled/skipped on that head, not successful protected delivery. +The six existing Ready lanes retain their separate approval/check requirements. +No merge SHA exists for either follow-up; no protected merge is claimed. + ### Current runtime and remaining acceptance - Official Compose project `lineageweave` is running. Read-only PostgreSQL aggregate rechecked during this follow-up: 43,189 source posts; the Voice-association table exists. No source name, record key, body, title, or credential was emitted. This is a descriptive whole-store count, not population inference or a claim that a diagnostic sample is representative. From ff72846f86f82f4b470578d69a9395e65fb31152 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Sun, 6 Sep 2026 23:53:13 +0900 Subject: [PATCH 6/6] test(api): pin unreadable-write no-retry contract --- docs/product-technical-gap-baseline.md | 18 ++++---------- frontend/src/api.test.ts | 34 +++++++++++++++++--------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/docs/product-technical-gap-baseline.md b/docs/product-technical-gap-baseline.md index 8e51c5a9a..ede83ccd1 100644 --- a/docs/product-technical-gap-baseline.md +++ b/docs/product-technical-gap-baseline.md @@ -1,6 +1,6 @@ # Product & Technical Gap Baseline -## Current bounded audit — 2026-09-06 +## Bounded audit snapshot — 2026-09-06T13:14:00Z Protected base: `83eba56149eb802cd63642c507c324c9976ec78e`. Tested implementation: `4f86ccc1a7885c55340adecff99bd273bbfe8bed`. The previously reviewed documentation head `149af3c2208a4041773fb88a708cfb7087e56c2c` @@ -8,7 +8,7 @@ is the direct child of tested implementation `3487b635f23166a964ac3ee69d5d19d12d which is the direct child of the protected base. The new tested implementation is the direct child of that documentation head. This document commit follows the tested implementation; it does not transfer hosted Checks between heads. -Queue rechecked on 2026-09-06 at 13:14 UTC: 122 open PRs (116 Draft, 6 Ready), 16 open issues. A second complete head inventory found no moved heads. This snapshot supersedes queue and runtime claims in the dated historical material below. +Queue rechecked on 2026-09-06 at 13:14 UTC: 122 open PRs (116 Draft, 6 Ready), 16 open issues. A second complete head inventory found no moved heads. This is time-scoped evidence only: live GitHub PR/check/runtime state supersedes it. It supersedes only older dated snapshots below, not later live state. ### Authority and evidence boundaries @@ -80,17 +80,9 @@ Existing `Chrome/StatusNotice/UnreadableResponse` renders the safe copy with exi - [Desktop](screenshots/unreadable-response-desktop-20260906.png) - [Mobile](screenshots/unreadable-response-mobile-20260906.png) -### Concurrent delivery-state change - -#960 was pushed at `38900637e646f8a6d29438406202e1e67cfc0417`, its -verified documentation thread was resolved, and normal Ready/auto-merge was -set at 13:19 UTC. A separate action under the same GitHub account converted it -to Draft at 13:21:20 UTC; GitHub disabled auto-merge because of that conversion. -The timeline records no reason for the Draft decision. This loop does not -continually reverse another actor's state changes. Required workflows remain -queued/cancelled/skipped on that head, not successful protected delivery. -The six existing Ready lanes retain their separate approval/check requirements. -No merge SHA exists for either follow-up; no protected merge is claimed. +### Dated delivery-state observation + +At 13:19 UTC #960 was Ready with auto-merge enabled; at 13:21:20 UTC it was Draft and auto-merge was disabled. These are historical state observations, not actor-intent or current-queue authority. The current PR body records unresolved no-retry and documentation-authority findings and keeps the candidate Draft until their repair plus required evidence is complete. Live GitHub PR/check state supersedes these timestamps. No merge SHA exists and no protected merge is claimed. ### Current runtime and remaining acceptance diff --git a/frontend/src/api.test.ts b/frontend/src/api.test.ts index abd6543b6..bd3121516 100644 --- a/frontend/src/api.test.ts +++ b/frontend/src/api.test.ts @@ -49,18 +49,28 @@ describe("backendFetch provider-error boundary", () => { { status, headers: { "Content-Type": "application/json" } }, ))); - for (const request of [ - () => fetchMe("synthetic-token"), - () => updateTenantConfig("synthetic-token", "Example tenant"), - ]) { - const error = await request().catch((reason: unknown) => reason); - expect(error).toBeInstanceOf(BackendError); - expect(error).toMatchObject({ - status, - message: "The service could not complete this request. Try again later.", - }); - expect(String(error)).not.toContain("synthetic-private-body"); - } + const error = await fetchMe("synthetic-token").catch((reason: unknown) => reason); + expect(error).toBeInstanceOf(BackendError); + expect(error).toMatchObject({ + status, + message: "The service could not complete this request. Try again later.", + }); + expect(String(error)).not.toContain("synthetic-private-body"); + }); + + it("does not retry an unreadable successful write response", async () => { + const fetchMock = vi.fn().mockImplementation(async () => new Response( + 'synthetic-private-body {"unfinished":', + { status: 201, headers: { "Content-Type": "application/json" } }, + )); + vi.stubGlobal("fetch", fetchMock); + + const error = await updateTenantConfig("synthetic-token", "Example tenant") + .catch((reason: unknown) => reason); + expect(error).toBeInstanceOf(BackendError); + expect(error).toMatchObject({ status: 201, message: "The service could not complete this request. Try again later." }); + expect(String(error)).not.toContain("synthetic-private-body"); + expect(fetchMock).toHaveBeenCalledTimes(1); }); it("hides body-stream failures after successful response headers", async () => {