Repository navigation
Releases: naiyixi/PureScience
Release list
案例市场索引(测试)
案例市场索引(测试)
这是案例市场的最小可用通道:一个索引文档 + 每个案例一个归档,都作为 release 资产发布。
case-marketplace.json— 索引本体(格式purescience.case-marketplacev1)internal-evaluation.tar.gz— 案例归档,内含manifest.json与files/
归档摘要(SHA-256,索引里逐字钉住,下载端先对摘要再解包):
7a739d4c7d8372e84d831800551c61cfb824ce02fac7d732996b1c9fa4f16e9e internal-evaluation.tar.gz
案例内容取自本机真实项目「内部评测」(6 个真实文件),不是示例工程。
PureScience v2.25.0
Maturity and Known Limitations
We label every capability the way we build it: ✅ shipped and verified on a real machine; 🚧 partly done, with the gap written next to it; 🗺️ planned. Each claim is traceable — a commit, a real-run log, or a record under docs/evidence/.
- ✅ Shipped and machine-verified: This patch removes a false negative, not a defect. v2.21.3 fixed CodeBuddy’s probe PATH, but the post-install check still re-read the settings snapshot; with two instances sharing one storage root,
settings-readmeasured 12,225 ms (11,918 ms held), so the freshly written value was invisible and a working install was reported as missing — the path and version were insettings.jsonall along, which is how the detector was exonerated and the re-read convicted. The check now asks the detector directly. This version fixed the three things a user meets first. The home page became scrollable (it wasmin-h-svhinside a viewport-fixing shell, so the gallery hanging off the bottom could not be reached), the demos became this machine’s own projects rather than evidence cards — a plaza built from projects that have run at least one session, six most-recent first, one click to open, timestamps instead of a session count, the evidence cards kept behind a disclosure — and CodeBuddy’s “installed but reported missing” was traced to npm’s#!/usr/bin/env nodeshim needing the node beside it: with a GUI-shaped PATH the probe gotenv: node: No such file or directory, so the binary’s own directory now goes on the probe’s PATH, and the installer verifies by probing instead of trusting npm’s exit code. ⌘⇧E expands or collapses every settled turn at once, writing the same per-group overrides a click writes. Open and named: search and native find still cannot reveal folded content, the connector panel’s domain sections cannot collapse, the plaza does not yet judge which runs went well, and the installer’s 277 MB composition is unmeasured. This version added four connectors and two numbers the panel had never stated. Two of those connectors were measured on this machine against the live services (LIPID MAPS:pubchem_cid=5997→LMST01010001,C27H46O,386.354866, with a name lookup refused by name because that API has none; OmniPath: directed interactions plus annotations plus enzyme→substrate edges, where/annotationsturned out to rejectorganismsand the result now says so instead of pretending). Two were not: every TCIA address answers000from here andsearch.foldseek.comis intermittent, so those readings are taken by a live probe on the CI runner and printed into its job log — the connectors ship with that gap named rather than with a claim this machine cannot support. Structure similarity also required a real capability: RCSB's public search has no working structure service (service:"structure"→ 400 for everyentry_idshape, 500 forvalue, whilechemical→ 204), so the tool uploads coordinates to FoldSeek through a newpostFormtransport. The connector panel now names each row's scientific area, groups the directory by area in place, and states 35 connectors · 290 tools from the registry. Notebook kernels can only write where they were told: a seatbelt profile denies writes first, allows the session and figures directories and the temp trees back, then denies the managed runtime last (last match wins) — measured asWROTEinside,PermissionErroroutside,PermissionErrorinside the runtime even under an allowed temp tree. Open and named: shell and background jobs are not confined, Linux and Windows have no write guard and claim none, and the packaged-lane screening certifications run again (p0109 passed / 12 skippedin 31.5 min) after the first-window budget went 30 s → 180 s. This version made the gallery's own claims harder to fake, and fixed the instrument that looks at the packaged lane. Three cards joined the home page (an import verdict you can recompute, four turns on one screen, a remote import refused), each holding readings and quotes that must appear verbatim in its named evidence file, with nine-language copy and rerun paths that have to exist. The packaged-lane checks that four certifications sit behind were only ever measured as1 skipped;PURESCIENCE_E2E_PACKAGED_SKIP_BYPASS=1now runs them and reproduces the failure here (1 failed), and the fixture writes its transcript intotest-results/because the app hands its agent a sanitised environment — the previous env-only path never reached the child. Three days of incidents are recorded entry by entry, and what is still broken stays on the label: the packaged screening lane still stalls at 0/N behind four named skips, the connector panel still groups by curation rather than discipline (35 connectors, 290 tools), and a cold start this file once put at 51.8 s measures 2.26 s to first window here against 3.20 s for the reference build on the same machine (three cold runs each, re-measured 2026-11-03) — that earlier number is withdrawn, not optimised around. This version made what leaves the app come back with its own receipts, and made a long session readable. The same session exported from the window, the web surface and the CLI produced a byte-identical payload —d36518b53a7dat 24 / 49 / 326 ms — with the whole-package digests differing only inexportedAt, because forging one timestamp to make three numbers agree would forge where the file came from; zip entries now carry a fixed 1980 epoch so the same content and stamp pack to the same bytes. An imported package recomputes its own claims: usage is derived fromconversation.jsonand compared field by field (state: match,turns: 1, with turns that recorded nothing counted apart), tool readings judge the manifest's declaration against the bytes it shipped (absent:no-tool-callsstays a named absence, never "0 tool calls checked"), the verdict is written into the import record and compared there, and a remote import is refused outright (401,local-only-authority) rather than quietly allowed. A settled turn keeps its own 24 px summary row with its own token total, and the three inputs stay apart — a number, "usage not recorded", or no line at all, never a 0 — while the row still folds: on a real window two turns producedgroups=2withexpanded: falsethentrue, and four turns producedsummaryRows:[24,24,24,24]in an 806 px viewport. That pair of readings took four attempts, and the reason is worth keeping: only one summary row ever appeared, and the cause was the deterministic test agent reusing the same tool-call ids every turn — the app correctly treated them as updates of the first turn's rows — so the product-side fix that shipped is the transcript merging the document's activity list with the live run's instead of letting the live run replace history. The CLI now serves an external MCP client: four read tools watched one audit line per call, then a write (archive_session) that resolves its target read-only, hangs the app's own approval card, and only then acts, refusing by name (not-approved,approval-unavailable,permission-gate-not-wired,already-archived) with the two ledgers paired by the sametoolCallId— cancelling leaves the row untouched, approving writesarchivedand the client reads it back (2 passed, 18.3 s). The home page gained five use-case cards whose every number and quote must appear verbatim in its own evidence file — a reading drifting by a tenth of a second turns that guard red — with a copy control answered by the main process's clipboard bridge rather than the window's denied one. DOCX tables are now drawn from what the document declares instead of what the renderer assumed: the table's six edges read fromtblBorders(falling back one level totblStyle), applied by rendered geometry, a cell's own rules winning, and a table that declares nothing left untouched — a 3 pt green double frame over a 0.5 pt grey grid measured 3.21 px and 0.80 px on a real window. Along the way a real defect came out of a red lane rather than from reading code: the egress proxy'sstop()waited forever when a client held its CONNECT tunnel open, whichcloseAllConnectionsdoes not cover. The previous version gave the wait a name and a price, and made the literature library check a retraction, follow citations both ways, and remember how far you read. Seven of the app's slowest requests were attributed from a week of its own log — 141 of 214 error/warn lines were slow IPC — before anything was changed.notebook:statere-parsed whole run documents on every read: on the real 60.1 MB file with 80 runs the hot median went 358.2 ms → 1.6 ms (cold 222.7 → 165.1) after an mtime+size cache that deliberately does not deep-copy, guarded by two tests that make the cache unable to lie.reviewer:get-for-sessionscanned two tables carrying only primary-key indexes behind an N+1 read and now has both indexes plus one query;notifications:get-snapshotqueued its reads behind writes and no longer does;artifacts:read-previewwas left alone on purpose, because an event-loop canary sampled at the same moment (261 ms against its 430 ms) accounts for most of it — rotation is reported rather than "fixed".sessions:save-sessionturned out to be two different slownesses: the 400 ms that dominated the samples was a database query in the post-write capture step (writeMs 3 ms,cpuShare 0), so it is skipped when no artifact version was finalised; the other half is a 60 MB document's write and rename (writeDiskMs 211,writeReplaceMs 176 ms) and is left at the I/O floor.settings:list-skillsis the honest non-result: the log says 662 ms median while a full in-process call takes 50.8 ms cold and 11.0 ms hot over 67 real skills, so parallel reads and an mtime+size cache landed (11.0 → 8.1 ms, plus askill list attributionline past 150 ms) and no target is claimed — without a baseline o...
PureScience v2.23.0
Maturity and Known Limitations
We label every capability the way we build it: ✅ shipped and verified on a real machine; 🚧 partly done, with the gap written next to it; 🗺️ planned. Each claim is traceable — a commit, a real-run log, or a record under docs/evidence/.
- ✅ Shipped and machine-verified: This patch removes a false negative, not a defect. v2.21.3 fixed CodeBuddy’s probe PATH, but the post-install check still re-read the settings snapshot; with two instances sharing one storage root,
settings-readmeasured 12,225 ms (11,918 ms held), so the freshly written value was invisible and a working install was reported as missing — the path and version were insettings.jsonall along, which is how the detector was exonerated and the re-read convicted. The check now asks the detector directly. This version fixed the three things a user meets first. The home page became scrollable (it wasmin-h-svhinside a viewport-fixing shell, so the gallery hanging off the bottom could not be reached), the demos became this machine’s own projects rather than evidence cards — a plaza built from projects that have run at least one session, six most-recent first, one click to open, timestamps instead of a session count, the evidence cards kept behind a disclosure — and CodeBuddy’s “installed but reported missing” was traced to npm’s#!/usr/bin/env nodeshim needing the node beside it: with a GUI-shaped PATH the probe gotenv: node: No such file or directory, so the binary’s own directory now goes on the probe’s PATH, and the installer verifies by probing instead of trusting npm’s exit code. ⌘⇧E expands or collapses every settled turn at once, writing the same per-group overrides a click writes. Open and named: search and native find still cannot reveal folded content, the connector panel’s domain sections cannot collapse, the plaza does not yet judge which runs went well, and the installer’s 277 MB composition is unmeasured. This version added four connectors and two numbers the panel had never stated. Two of those connectors were measured on this machine against the live services (LIPID MAPS:pubchem_cid=5997→LMST01010001,C27H46O,386.354866, with a name lookup refused by name because that API has none; OmniPath: directed interactions plus annotations plus enzyme→substrate edges, where/annotationsturned out to rejectorganismsand the result now says so instead of pretending). Two were not: every TCIA address answers000from here andsearch.foldseek.comis intermittent, so those readings are taken by a live probe on the CI runner and printed into its job log — the connectors ship with that gap named rather than with a claim this machine cannot support. Structure similarity also required a real capability: RCSB's public search has no working structure service (service:"structure"→ 400 for everyentry_idshape, 500 forvalue, whilechemical→ 204), so the tool uploads coordinates to FoldSeek through a newpostFormtransport. The connector panel now names each row's scientific area, groups the directory by area in place, and states 35 connectors · 290 tools from the registry. Notebook kernels can only write where they were told: a seatbelt profile denies writes first, allows the session and figures directories and the temp trees back, then denies the managed runtime last (last match wins) — measured asWROTEinside,PermissionErroroutside,PermissionErrorinside the runtime even under an allowed temp tree. Open and named: shell and background jobs are not confined, Linux and Windows have no write guard and claim none, and the packaged-lane screening certifications run again (p0109 passed / 12 skippedin 31.5 min) after the first-window budget went 30 s → 180 s. This version made the gallery's own claims harder to fake, and fixed the instrument that looks at the packaged lane. Three cards joined the home page (an import verdict you can recompute, four turns on one screen, a remote import refused), each holding readings and quotes that must appear verbatim in its named evidence file, with nine-language copy and rerun paths that have to exist. The packaged-lane checks that four certifications sit behind were only ever measured as1 skipped;PURESCIENCE_E2E_PACKAGED_SKIP_BYPASS=1now runs them and reproduces the failure here (1 failed), and the fixture writes its transcript intotest-results/because the app hands its agent a sanitised environment — the previous env-only path never reached the child. Three days of incidents are recorded entry by entry, and what is still broken stays on the label: the packaged screening lane still stalls at 0/N behind four named skips, the connector panel still groups by curation rather than discipline (35 connectors, 290 tools), and a cold start this file once put at 51.8 s measures 2.26 s to first window here against 3.20 s for the reference build on the same machine (three cold runs each, re-measured 2026-11-03) — that earlier number is withdrawn, not optimised around. This version made what leaves the app come back with its own receipts, and made a long session readable. The same session exported from the window, the web surface and the CLI produced a byte-identical payload —d36518b53a7dat 24 / 49 / 326 ms — with the whole-package digests differing only inexportedAt, because forging one timestamp to make three numbers agree would forge where the file came from; zip entries now carry a fixed 1980 epoch so the same content and stamp pack to the same bytes. An imported package recomputes its own claims: usage is derived fromconversation.jsonand compared field by field (state: match,turns: 1, with turns that recorded nothing counted apart), tool readings judge the manifest's declaration against the bytes it shipped (absent:no-tool-callsstays a named absence, never "0 tool calls checked"), the verdict is written into the import record and compared there, and a remote import is refused outright (401,local-only-authority) rather than quietly allowed. A settled turn keeps its own 24 px summary row with its own token total, and the three inputs stay apart — a number, "usage not recorded", or no line at all, never a 0 — while the row still folds: on a real window two turns producedgroups=2withexpanded: falsethentrue, and four turns producedsummaryRows:[24,24,24,24]in an 806 px viewport. That pair of readings took four attempts, and the reason is worth keeping: only one summary row ever appeared, and the cause was the deterministic test agent reusing the same tool-call ids every turn — the app correctly treated them as updates of the first turn's rows — so the product-side fix that shipped is the transcript merging the document's activity list with the live run's instead of letting the live run replace history. The CLI now serves an external MCP client: four read tools watched one audit line per call, then a write (archive_session) that resolves its target read-only, hangs the app's own approval card, and only then acts, refusing by name (not-approved,approval-unavailable,permission-gate-not-wired,already-archived) with the two ledgers paired by the sametoolCallId— cancelling leaves the row untouched, approving writesarchivedand the client reads it back (2 passed, 18.3 s). The home page gained five use-case cards whose every number and quote must appear verbatim in its own evidence file — a reading drifting by a tenth of a second turns that guard red — with a copy control answered by the main process's clipboard bridge rather than the window's denied one. DOCX tables are now drawn from what the document declares instead of what the renderer assumed: the table's six edges read fromtblBorders(falling back one level totblStyle), applied by rendered geometry, a cell's own rules winning, and a table that declares nothing left untouched — a 3 pt green double frame over a 0.5 pt grey grid measured 3.21 px and 0.80 px on a real window. Along the way a real defect came out of a red lane rather than from reading code: the egress proxy'sstop()waited forever when a client held its CONNECT tunnel open, whichcloseAllConnectionsdoes not cover. The previous version gave the wait a name and a price, and made the literature library check a retraction, follow citations both ways, and remember how far you read. Seven of the app's slowest requests were attributed from a week of its own log — 141 of 214 error/warn lines were slow IPC — before anything was changed.notebook:statere-parsed whole run documents on every read: on the real 60.1 MB file with 80 runs the hot median went 358.2 ms → 1.6 ms (cold 222.7 → 165.1) after an mtime+size cache that deliberately does not deep-copy, guarded by two tests that make the cache unable to lie.reviewer:get-for-sessionscanned two tables carrying only primary-key indexes behind an N+1 read and now has both indexes plus one query;notifications:get-snapshotqueued its reads behind writes and no longer does;artifacts:read-previewwas left alone on purpose, because an event-loop canary sampled at the same moment (261 ms against its 430 ms) accounts for most of it — rotation is reported rather than "fixed".sessions:save-sessionturned out to be two different slownesses: the 400 ms that dominated the samples was a database query in the post-write capture step (writeMs 3 ms,cpuShare 0), so it is skipped when no artifact version was finalised; the other half is a 60 MB document's write and rename (writeDiskMs 211,writeReplaceMs 176 ms) and is left at the I/O floor.settings:list-skillsis the honest non-result: the log says 662 ms median while a full in-process call takes 50.8 ms cold and 11.0 ms hot over 67 real skills, so parallel reads and an mtime+size cache landed (11.0 → 8.1 ms, plus askill list attributionline past 150 ms) and no target is claimed — without a baseline o...
PureScience v2.21.4
Maturity and Known Limitations
We label every capability the way we build it: ✅ shipped and verified on a real machine; 🚧 partly done, with the gap written next to it; 🗺️ planned. Each claim is traceable — a commit, a real-run log, or a record under docs/evidence/.
- ✅ Shipped and machine-verified: This patch removes a false negative, not a defect. v2.21.3 fixed CodeBuddy’s probe PATH, but the post-install check still re-read the settings snapshot; with two instances sharing one storage root,
settings-readmeasured 12,225 ms (11,918 ms held), so the freshly written value was invisible and a working install was reported as missing — the path and version were insettings.jsonall along, which is how the detector was exonerated and the re-read convicted. The check now asks the detector directly. This version fixed the three things a user meets first. The home page became scrollable (it wasmin-h-svhinside a viewport-fixing shell, so the gallery hanging off the bottom could not be reached), the demos became this machine’s own projects rather than evidence cards — a plaza built from projects that have run at least one session, six most-recent first, one click to open, timestamps instead of a session count, the evidence cards kept behind a disclosure — and CodeBuddy’s “installed but reported missing” was traced to npm’s#!/usr/bin/env nodeshim needing the node beside it: with a GUI-shaped PATH the probe gotenv: node: No such file or directory, so the binary’s own directory now goes on the probe’s PATH, and the installer verifies by probing instead of trusting npm’s exit code. ⌘⇧E expands or collapses every settled turn at once, writing the same per-group overrides a click writes. Open and named: search and native find still cannot reveal folded content, the connector panel’s domain sections cannot collapse, the plaza does not yet judge which runs went well, and the installer’s 277 MB composition is unmeasured. This version added four connectors and two numbers the panel had never stated. Two of those connectors were measured on this machine against the live services (LIPID MAPS:pubchem_cid=5997→LMST01010001,C27H46O,386.354866, with a name lookup refused by name because that API has none; OmniPath: directed interactions plus annotations plus enzyme→substrate edges, where/annotationsturned out to rejectorganismsand the result now says so instead of pretending). Two were not: every TCIA address answers000from here andsearch.foldseek.comis intermittent, so those readings are taken by a live probe on the CI runner and printed into its job log — the connectors ship with that gap named rather than with a claim this machine cannot support. Structure similarity also required a real capability: RCSB's public search has no working structure service (service:"structure"→ 400 for everyentry_idshape, 500 forvalue, whilechemical→ 204), so the tool uploads coordinates to FoldSeek through a newpostFormtransport. The connector panel now names each row's scientific area, groups the directory by area in place, and states 35 connectors · 290 tools from the registry. Notebook kernels can only write where they were told: a seatbelt profile denies writes first, allows the session and figures directories and the temp trees back, then denies the managed runtime last (last match wins) — measured asWROTEinside,PermissionErroroutside,PermissionErrorinside the runtime even under an allowed temp tree. Open and named: shell and background jobs are not confined, Linux and Windows have no write guard and claim none, and the packaged-lane screening certifications run again (p0109 passed / 12 skippedin 31.5 min) after the first-window budget went 30 s → 180 s. This version made the gallery's own claims harder to fake, and fixed the instrument that looks at the packaged lane. Three cards joined the home page (an import verdict you can recompute, four turns on one screen, a remote import refused), each holding readings and quotes that must appear verbatim in its named evidence file, with nine-language copy and rerun paths that have to exist. The packaged-lane checks that four certifications sit behind were only ever measured as1 skipped;PURESCIENCE_E2E_PACKAGED_SKIP_BYPASS=1now runs them and reproduces the failure here (1 failed), and the fixture writes its transcript intotest-results/because the app hands its agent a sanitised environment — the previous env-only path never reached the child. Three days of incidents are recorded entry by entry, and what is still broken stays on the label: the packaged screening lane still stalls at 0/N behind four named skips, the connector panel still groups by curation rather than discipline (35 connectors, 290 tools), and a measured 51.8 s cold start is unoptimised. This version made what leaves the app come back with its own receipts, and made a long session readable. The same session exported from the window, the web surface and the CLI produced a byte-identical payload —d36518b53a7dat 24 / 49 / 326 ms — with the whole-package digests differing only inexportedAt, because forging one timestamp to make three numbers agree would forge where the file came from; zip entries now carry a fixed 1980 epoch so the same content and stamp pack to the same bytes. An imported package recomputes its own claims: usage is derived fromconversation.jsonand compared field by field (state: match,turns: 1, with turns that recorded nothing counted apart), tool readings judge the manifest's declaration against the bytes it shipped (absent:no-tool-callsstays a named absence, never "0 tool calls checked"), the verdict is written into the import record and compared there, and a remote import is refused outright (401,local-only-authority) rather than quietly allowed. A settled turn keeps its own 24 px summary row with its own token total, and the three inputs stay apart — a number, "usage not recorded", or no line at all, never a 0 — while the row still folds: on a real window two turns producedgroups=2withexpanded: falsethentrue, and four turns producedsummaryRows:[24,24,24,24]in an 806 px viewport. That pair of readings took four attempts, and the reason is worth keeping: only one summary row ever appeared, and the cause was the deterministic test agent reusing the same tool-call ids every turn — the app correctly treated them as updates of the first turn's rows — so the product-side fix that shipped is the transcript merging the document's activity list with the live run's instead of letting the live run replace history. The CLI now serves an external MCP client: four read tools watched one audit line per call, then a write (archive_session) that resolves its target read-only, hangs the app's own approval card, and only then acts, refusing by name (not-approved,approval-unavailable,permission-gate-not-wired,already-archived) with the two ledgers paired by the sametoolCallId— cancelling leaves the row untouched, approving writesarchivedand the client reads it back (2 passed, 18.3 s). The home page gained five use-case cards whose every number and quote must appear verbatim in its own evidence file — a reading drifting by a tenth of a second turns that guard red — with a copy control answered by the main process's clipboard bridge rather than the window's denied one. DOCX tables are now drawn from what the document declares instead of what the renderer assumed: the table's six edges read fromtblBorders(falling back one level totblStyle), applied by rendered geometry, a cell's own rules winning, and a table that declares nothing left untouched — a 3 pt green double frame over a 0.5 pt grey grid measured 3.21 px and 0.80 px on a real window. Along the way a real defect came out of a red lane rather than from reading code: the egress proxy'sstop()waited forever when a client held its CONNECT tunnel open, whichcloseAllConnectionsdoes not cover. The previous version gave the wait a name and a price, and made the literature library check a retraction, follow citations both ways, and remember how far you read. Seven of the app's slowest requests were attributed from a week of its own log — 141 of 214 error/warn lines were slow IPC — before anything was changed.notebook:statere-parsed whole run documents on every read: on the real 60.1 MB file with 80 runs the hot median went 358.2 ms → 1.6 ms (cold 222.7 → 165.1) after an mtime+size cache that deliberately does not deep-copy, guarded by two tests that make the cache unable to lie.reviewer:get-for-sessionscanned two tables carrying only primary-key indexes behind an N+1 read and now has both indexes plus one query;notifications:get-snapshotqueued its reads behind writes and no longer does;artifacts:read-previewwas left alone on purpose, because an event-loop canary sampled at the same moment (261 ms against its 430 ms) accounts for most of it — rotation is reported rather than "fixed".sessions:save-sessionturned out to be two different slownesses: the 400 ms that dominated the samples was a database query in the post-write capture step (writeMs 3 ms,cpuShare 0), so it is skipped when no artifact version was finalised; the other half is a 60 MB document's write and rename (writeDiskMs 211,writeReplaceMs 176 ms) and is left at the I/O floor.settings:list-skillsis the honest non-result: the log says 662 ms median while a full in-process call takes 50.8 ms cold and 11.0 ms hot over 67 real skills, so parallel reads and an mtime+size cache landed (11.0 → 8.1 ms, plus askill list attributionline past 150 ms) and no target is claimed — without a baseline of the same size, "we improved it" would stand in for "it is fast enough". An event-loop stall used to end atno-cpu-in-process, which is a measurement and not a cause: every record now carries `garbage-co...
PureScience v2.21.3
Maturity and Known Limitations
We label every capability the way we build it: ✅ shipped and verified on a real machine; 🚧 partly done, with the gap written next to it; 🗺️ planned. Each claim is traceable — a commit, a real-run log, or a record under docs/evidence/.
- ✅ Shipped and machine-verified: This version fixed the three things a user meets first. The home page became scrollable (it was
min-h-svhinside a viewport-fixing shell, so the gallery hanging off the bottom could not be reached), the demos became this machine’s own projects rather than evidence cards — a plaza built from projects that have run at least one session, six most-recent first, one click to open, timestamps instead of a session count, the evidence cards kept behind a disclosure — and CodeBuddy’s “installed but reported missing” was traced to npm’s#!/usr/bin/env nodeshim needing the node beside it: with a GUI-shaped PATH the probe gotenv: node: No such file or directory, so the binary’s own directory now goes on the probe’s PATH, and the installer verifies by probing instead of trusting npm’s exit code. ⌘⇧E expands or collapses every settled turn at once, writing the same per-group overrides a click writes. Open and named: search and native find still cannot reveal folded content, the connector panel’s domain sections cannot collapse, the plaza does not yet judge which runs went well, and the installer’s 277 MB composition is unmeasured. This version added four connectors and two numbers the panel had never stated. Two of those connectors were measured on this machine against the live services (LIPID MAPS:pubchem_cid=5997→LMST01010001,C27H46O,386.354866, with a name lookup refused by name because that API has none; OmniPath: directed interactions plus annotations plus enzyme→substrate edges, where/annotationsturned out to rejectorganismsand the result now says so instead of pretending). Two were not: every TCIA address answers000from here andsearch.foldseek.comis intermittent, so those readings are taken by a live probe on the CI runner and printed into its job log — the connectors ship with that gap named rather than with a claim this machine cannot support. Structure similarity also required a real capability: RCSB's public search has no working structure service (service:"structure"→ 400 for everyentry_idshape, 500 forvalue, whilechemical→ 204), so the tool uploads coordinates to FoldSeek through a newpostFormtransport. The connector panel now names each row's scientific area, groups the directory by area in place, and states 35 connectors · 290 tools from the registry. Notebook kernels can only write where they were told: a seatbelt profile denies writes first, allows the session and figures directories and the temp trees back, then denies the managed runtime last (last match wins) — measured asWROTEinside,PermissionErroroutside,PermissionErrorinside the runtime even under an allowed temp tree. Open and named: shell and background jobs are not confined, Linux and Windows have no write guard and claim none, and the packaged-lane screening certifications run again (p0109 passed / 12 skippedin 31.5 min) after the first-window budget went 30 s → 180 s. This version made the gallery's own claims harder to fake, and fixed the instrument that looks at the packaged lane. Three cards joined the home page (an import verdict you can recompute, four turns on one screen, a remote import refused), each holding readings and quotes that must appear verbatim in its named evidence file, with nine-language copy and rerun paths that have to exist. The packaged-lane checks that four certifications sit behind were only ever measured as1 skipped;PURESCIENCE_E2E_PACKAGED_SKIP_BYPASS=1now runs them and reproduces the failure here (1 failed), and the fixture writes its transcript intotest-results/because the app hands its agent a sanitised environment — the previous env-only path never reached the child. Three days of incidents are recorded entry by entry, and what is still broken stays on the label: the packaged screening lane still stalls at 0/N behind four named skips, the connector panel still groups by curation rather than discipline (35 connectors, 290 tools), and a measured 51.8 s cold start is unoptimised. This version made what leaves the app come back with its own receipts, and made a long session readable. The same session exported from the window, the web surface and the CLI produced a byte-identical payload —d36518b53a7dat 24 / 49 / 326 ms — with the whole-package digests differing only inexportedAt, because forging one timestamp to make three numbers agree would forge where the file came from; zip entries now carry a fixed 1980 epoch so the same content and stamp pack to the same bytes. An imported package recomputes its own claims: usage is derived fromconversation.jsonand compared field by field (state: match,turns: 1, with turns that recorded nothing counted apart), tool readings judge the manifest's declaration against the bytes it shipped (absent:no-tool-callsstays a named absence, never "0 tool calls checked"), the verdict is written into the import record and compared there, and a remote import is refused outright (401,local-only-authority) rather than quietly allowed. A settled turn keeps its own 24 px summary row with its own token total, and the three inputs stay apart — a number, "usage not recorded", or no line at all, never a 0 — while the row still folds: on a real window two turns producedgroups=2withexpanded: falsethentrue, and four turns producedsummaryRows:[24,24,24,24]in an 806 px viewport. That pair of readings took four attempts, and the reason is worth keeping: only one summary row ever appeared, and the cause was the deterministic test agent reusing the same tool-call ids every turn — the app correctly treated them as updates of the first turn's rows — so the product-side fix that shipped is the transcript merging the document's activity list with the live run's instead of letting the live run replace history. The CLI now serves an external MCP client: four read tools watched one audit line per call, then a write (archive_session) that resolves its target read-only, hangs the app's own approval card, and only then acts, refusing by name (not-approved,approval-unavailable,permission-gate-not-wired,already-archived) with the two ledgers paired by the sametoolCallId— cancelling leaves the row untouched, approving writesarchivedand the client reads it back (2 passed, 18.3 s). The home page gained five use-case cards whose every number and quote must appear verbatim in its own evidence file — a reading drifting by a tenth of a second turns that guard red — with a copy control answered by the main process's clipboard bridge rather than the window's denied one. DOCX tables are now drawn from what the document declares instead of what the renderer assumed: the table's six edges read fromtblBorders(falling back one level totblStyle), applied by rendered geometry, a cell's own rules winning, and a table that declares nothing left untouched — a 3 pt green double frame over a 0.5 pt grey grid measured 3.21 px and 0.80 px on a real window. Along the way a real defect came out of a red lane rather than from reading code: the egress proxy'sstop()waited forever when a client held its CONNECT tunnel open, whichcloseAllConnectionsdoes not cover. The previous version gave the wait a name and a price, and made the literature library check a retraction, follow citations both ways, and remember how far you read. Seven of the app's slowest requests were attributed from a week of its own log — 141 of 214 error/warn lines were slow IPC — before anything was changed.notebook:statere-parsed whole run documents on every read: on the real 60.1 MB file with 80 runs the hot median went 358.2 ms → 1.6 ms (cold 222.7 → 165.1) after an mtime+size cache that deliberately does not deep-copy, guarded by two tests that make the cache unable to lie.reviewer:get-for-sessionscanned two tables carrying only primary-key indexes behind an N+1 read and now has both indexes plus one query;notifications:get-snapshotqueued its reads behind writes and no longer does;artifacts:read-previewwas left alone on purpose, because an event-loop canary sampled at the same moment (261 ms against its 430 ms) accounts for most of it — rotation is reported rather than "fixed".sessions:save-sessionturned out to be two different slownesses: the 400 ms that dominated the samples was a database query in the post-write capture step (writeMs 3 ms,cpuShare 0), so it is skipped when no artifact version was finalised; the other half is a 60 MB document's write and rename (writeDiskMs 211,writeReplaceMs 176 ms) and is left at the I/O floor.settings:list-skillsis the honest non-result: the log says 662 ms median while a full in-process call takes 50.8 ms cold and 11.0 ms hot over 67 real skills, so parallel reads and an mtime+size cache landed (11.0 → 8.1 ms, plus askill list attributionline past 150 ms) and no target is claimed — without a baseline of the same size, "we improved it" would stand in for "it is fast enough". An event-loop stall used to end atno-cpu-in-process, which is a measurement and not a cause: every record now carriesgarbage-collection,fs-queue-saturated,cpu-in-process,unexplainedorunmeasured, withunexplainednaming the milliseconds it ruled out andunmeasuredsaying an instrument was missing — merging those two is how "nobody was watching" starts reading as "the machine was fine"; the GC observer and the thread-pool canary are started with the app rather than when a stall is noticed, and the 20 ms saturation threshold comes from 57 real canary samples (min 1 / median 24 / p90 180 / max 686 ms). The literature ...
Nightly (latest main)
Automated build of the latest main (10397fb).
right-click → Open, or run xattr -dr com.apple.quarantine "<app>".
PureScience v2.21.2
Maturity and Known Limitations
We label every capability the way we build it: ✅ shipped and verified on a real machine; 🚧 partly done, with the gap written next to it; 🗺️ planned. Each claim is traceable — a commit, a real-run log, or a record under docs/evidence/.
- ✅ Shipped and machine-verified: This version added four connectors and two numbers the panel had never stated. Two of those connectors were measured on this machine against the live services (LIPID MAPS:
pubchem_cid=5997→LMST01010001,C27H46O,386.354866, with a name lookup refused by name because that API has none; OmniPath: directed interactions plus annotations plus enzyme→substrate edges, where/annotationsturned out to rejectorganismsand the result now says so instead of pretending). Two were not: every TCIA address answers000from here andsearch.foldseek.comis intermittent, so those readings are taken by a live probe on the CI runner and printed into its job log — the connectors ship with that gap named rather than with a claim this machine cannot support. Structure similarity also required a real capability: RCSB's public search has no working structure service (service:"structure"→ 400 for everyentry_idshape, 500 forvalue, whilechemical→ 204), so the tool uploads coordinates to FoldSeek through a newpostFormtransport. The connector panel now names each row's scientific area, groups the directory by area in place, and states 35 connectors · 290 tools from the registry. Notebook kernels can only write where they were told: a seatbelt profile denies writes first, allows the session and figures directories and the temp trees back, then denies the managed runtime last (last match wins) — measured asWROTEinside,PermissionErroroutside,PermissionErrorinside the runtime even under an allowed temp tree. Open and named: shell and background jobs are not confined, Linux and Windows have no write guard and claim none, and the packaged-lane screening certifications run again (p0109 passed / 12 skippedin 31.5 min) after the first-window budget went 30 s → 180 s. This version made the gallery's own claims harder to fake, and fixed the instrument that looks at the packaged lane. Three cards joined the home page (an import verdict you can recompute, four turns on one screen, a remote import refused), each holding readings and quotes that must appear verbatim in its named evidence file, with nine-language copy and rerun paths that have to exist. The packaged-lane checks that four certifications sit behind were only ever measured as1 skipped;PURESCIENCE_E2E_PACKAGED_SKIP_BYPASS=1now runs them and reproduces the failure here (1 failed), and the fixture writes its transcript intotest-results/because the app hands its agent a sanitised environment — the previous env-only path never reached the child. Three days of incidents are recorded entry by entry, and what is still broken stays on the label: the packaged screening lane still stalls at 0/N behind four named skips, the connector panel still groups by curation rather than discipline (35 connectors, 290 tools), and a measured 51.8 s cold start is unoptimised. This version made what leaves the app come back with its own receipts, and made a long session readable. The same session exported from the window, the web surface and the CLI produced a byte-identical payload —d36518b53a7dat 24 / 49 / 326 ms — with the whole-package digests differing only inexportedAt, because forging one timestamp to make three numbers agree would forge where the file came from; zip entries now carry a fixed 1980 epoch so the same content and stamp pack to the same bytes. An imported package recomputes its own claims: usage is derived fromconversation.jsonand compared field by field (state: match,turns: 1, with turns that recorded nothing counted apart), tool readings judge the manifest's declaration against the bytes it shipped (absent:no-tool-callsstays a named absence, never "0 tool calls checked"), the verdict is written into the import record and compared there, and a remote import is refused outright (401,local-only-authority) rather than quietly allowed. A settled turn keeps its own 24 px summary row with its own token total, and the three inputs stay apart — a number, "usage not recorded", or no line at all, never a 0 — while the row still folds: on a real window two turns producedgroups=2withexpanded: falsethentrue, and four turns producedsummaryRows:[24,24,24,24]in an 806 px viewport. That pair of readings took four attempts, and the reason is worth keeping: only one summary row ever appeared, and the cause was the deterministic test agent reusing the same tool-call ids every turn — the app correctly treated them as updates of the first turn's rows — so the product-side fix that shipped is the transcript merging the document's activity list with the live run's instead of letting the live run replace history. The CLI now serves an external MCP client: four read tools watched one audit line per call, then a write (archive_session) that resolves its target read-only, hangs the app's own approval card, and only then acts, refusing by name (not-approved,approval-unavailable,permission-gate-not-wired,already-archived) with the two ledgers paired by the sametoolCallId— cancelling leaves the row untouched, approving writesarchivedand the client reads it back (2 passed, 18.3 s). The home page gained five use-case cards whose every number and quote must appear verbatim in its own evidence file — a reading drifting by a tenth of a second turns that guard red — with a copy control answered by the main process's clipboard bridge rather than the window's denied one. DOCX tables are now drawn from what the document declares instead of what the renderer assumed: the table's six edges read fromtblBorders(falling back one level totblStyle), applied by rendered geometry, a cell's own rules winning, and a table that declares nothing left untouched — a 3 pt green double frame over a 0.5 pt grey grid measured 3.21 px and 0.80 px on a real window. Along the way a real defect came out of a red lane rather than from reading code: the egress proxy'sstop()waited forever when a client held its CONNECT tunnel open, whichcloseAllConnectionsdoes not cover. The previous version gave the wait a name and a price, and made the literature library check a retraction, follow citations both ways, and remember how far you read. Seven of the app's slowest requests were attributed from a week of its own log — 141 of 214 error/warn lines were slow IPC — before anything was changed.notebook:statere-parsed whole run documents on every read: on the real 60.1 MB file with 80 runs the hot median went 358.2 ms → 1.6 ms (cold 222.7 → 165.1) after an mtime+size cache that deliberately does not deep-copy, guarded by two tests that make the cache unable to lie.reviewer:get-for-sessionscanned two tables carrying only primary-key indexes behind an N+1 read and now has both indexes plus one query;notifications:get-snapshotqueued its reads behind writes and no longer does;artifacts:read-previewwas left alone on purpose, because an event-loop canary sampled at the same moment (261 ms against its 430 ms) accounts for most of it — rotation is reported rather than "fixed".sessions:save-sessionturned out to be two different slownesses: the 400 ms that dominated the samples was a database query in the post-write capture step (writeMs 3 ms,cpuShare 0), so it is skipped when no artifact version was finalised; the other half is a 60 MB document's write and rename (writeDiskMs 211,writeReplaceMs 176 ms) and is left at the I/O floor.settings:list-skillsis the honest non-result: the log says 662 ms median while a full in-process call takes 50.8 ms cold and 11.0 ms hot over 67 real skills, so parallel reads and an mtime+size cache landed (11.0 → 8.1 ms, plus askill list attributionline past 150 ms) and no target is claimed — without a baseline of the same size, "we improved it" would stand in for "it is fast enough". An event-loop stall used to end atno-cpu-in-process, which is a measurement and not a cause: every record now carriesgarbage-collection,fs-queue-saturated,cpu-in-process,unexplainedorunmeasured, withunexplainednaming the milliseconds it ruled out andunmeasuredsaying an instrument was missing — merging those two is how "nobody was watching" starts reading as "the machine was fine"; the GC observer and the thread-pool canary are started with the app rather than when a stall is noticed, and the 20 ms saturation threshold comes from 57 real canary samples (min 1 / median 24 / p90 180 / max 686 ms). The literature library grew three connected pieces: a retraction check reading four independent Crossref signals instead of onlyrelation['is-retracted-by'](which hit 0 of 12 sampled withdrawn works, which is why the old checker found nothing), with the winning signal and the check time stored on the reference and the export gate reading only what was already stored; citation discovery in both directions on live data, where one English record reports 83,700 citing works and reads 50 of them while the result says so (incomplete), four Chinese-source records return 47–50 citations each, andlanguage: zhcatches only 2 of 200 rows so it is not the criterion; and a three-state reading status (unread / reading / read) that survives a restart, filters the list, and counts what the text cites but you have not finished — where "reading" counts as unfinished, since onlyreadis finished. Previously it made a framework selection stop being silently ignored: two hand-maintained whitelists listed three of four framework ids, socodebuddysurvived the settings parser and vanished on the next read, and session persistence dropped it too. *...
PureScience v2.21.1
Maturity and Known Limitations
We label every capability the way we build it: ✅ shipped and verified on a real machine; 🚧 partly done, with the gap written next to it; 🗺️ planned. Each claim is traceable — a commit, a real-run log, or a record under docs/evidence/.
- ✅ Shipped and machine-verified: This version made the gallery's own claims harder to fake, and fixed the instrument that looks at the packaged lane. Three cards joined the home page (an import verdict you can recompute, four turns on one screen, a remote import refused), each holding readings and quotes that must appear verbatim in its named evidence file, with nine-language copy and rerun paths that have to exist. The packaged-lane checks that four certifications sit behind were only ever measured as
1 skipped;PURESCIENCE_E2E_PACKAGED_SKIP_BYPASS=1now runs them and reproduces the failure here (1 failed), and the fixture writes its transcript intotest-results/because the app hands its agent a sanitised environment — the previous env-only path never reached the child. Three days of incidents are recorded entry by entry, and what is still broken stays on the label: the packaged screening lane still stalls at 0/N behind four named skips, the connector panel still groups by curation rather than discipline (31 connectors, 282 tools), and a measured 51.8 s cold start is unoptimised. This version made what leaves the app come back with its own receipts, and made a long session readable. The same session exported from the window, the web surface and the CLI produced a byte-identical payload —d36518b53a7dat 24 / 49 / 326 ms — with the whole-package digests differing only inexportedAt, because forging one timestamp to make three numbers agree would forge where the file came from; zip entries now carry a fixed 1980 epoch so the same content and stamp pack to the same bytes. An imported package recomputes its own claims: usage is derived fromconversation.jsonand compared field by field (state: match,turns: 1, with turns that recorded nothing counted apart), tool readings judge the manifest's declaration against the bytes it shipped (absent:no-tool-callsstays a named absence, never "0 tool calls checked"), the verdict is written into the import record and compared there, and a remote import is refused outright (401,local-only-authority) rather than quietly allowed. A settled turn keeps its own 24 px summary row with its own token total, and the three inputs stay apart — a number, "usage not recorded", or no line at all, never a 0 — while the row still folds: on a real window two turns producedgroups=2withexpanded: falsethentrue, and four turns producedsummaryRows:[24,24,24,24]in an 806 px viewport. That pair of readings took four attempts, and the reason is worth keeping: only one summary row ever appeared, and the cause was the deterministic test agent reusing the same tool-call ids every turn — the app correctly treated them as updates of the first turn's rows — so the product-side fix that shipped is the transcript merging the document's activity list with the live run's instead of letting the live run replace history. The CLI now serves an external MCP client: four read tools watched one audit line per call, then a write (archive_session) that resolves its target read-only, hangs the app's own approval card, and only then acts, refusing by name (not-approved,approval-unavailable,permission-gate-not-wired,already-archived) with the two ledgers paired by the sametoolCallId— cancelling leaves the row untouched, approving writesarchivedand the client reads it back (2 passed, 18.3 s). The home page gained five use-case cards whose every number and quote must appear verbatim in its own evidence file — a reading drifting by a tenth of a second turns that guard red — with a copy control answered by the main process's clipboard bridge rather than the window's denied one. DOCX tables are now drawn from what the document declares instead of what the renderer assumed: the table's six edges read fromtblBorders(falling back one level totblStyle), applied by rendered geometry, a cell's own rules winning, and a table that declares nothing left untouched — a 3 pt green double frame over a 0.5 pt grey grid measured 3.21 px and 0.80 px on a real window. Along the way a real defect came out of a red lane rather than from reading code: the egress proxy'sstop()waited forever when a client held its CONNECT tunnel open, whichcloseAllConnectionsdoes not cover. The previous version gave the wait a name and a price, and made the literature library check a retraction, follow citations both ways, and remember how far you read. Seven of the app's slowest requests were attributed from a week of its own log — 141 of 214 error/warn lines were slow IPC — before anything was changed.notebook:statere-parsed whole run documents on every read: on the real 60.1 MB file with 80 runs the hot median went 358.2 ms → 1.6 ms (cold 222.7 → 165.1) after an mtime+size cache that deliberately does not deep-copy, guarded by two tests that make the cache unable to lie.reviewer:get-for-sessionscanned two tables carrying only primary-key indexes behind an N+1 read and now has both indexes plus one query;notifications:get-snapshotqueued its reads behind writes and no longer does;artifacts:read-previewwas left alone on purpose, because an event-loop canary sampled at the same moment (261 ms against its 430 ms) accounts for most of it — rotation is reported rather than "fixed".sessions:save-sessionturned out to be two different slownesses: the 400 ms that dominated the samples was a database query in the post-write capture step (writeMs 3 ms,cpuShare 0), so it is skipped when no artifact version was finalised; the other half is a 60 MB document's write and rename (writeDiskMs 211,writeReplaceMs 176 ms) and is left at the I/O floor.settings:list-skillsis the honest non-result: the log says 662 ms median while a full in-process call takes 50.8 ms cold and 11.0 ms hot over 67 real skills, so parallel reads and an mtime+size cache landed (11.0 → 8.1 ms, plus askill list attributionline past 150 ms) and no target is claimed — without a baseline of the same size, "we improved it" would stand in for "it is fast enough". An event-loop stall used to end atno-cpu-in-process, which is a measurement and not a cause: every record now carriesgarbage-collection,fs-queue-saturated,cpu-in-process,unexplainedorunmeasured, withunexplainednaming the milliseconds it ruled out andunmeasuredsaying an instrument was missing — merging those two is how "nobody was watching" starts reading as "the machine was fine"; the GC observer and the thread-pool canary are started with the app rather than when a stall is noticed, and the 20 ms saturation threshold comes from 57 real canary samples (min 1 / median 24 / p90 180 / max 686 ms). The literature library grew three connected pieces: a retraction check reading four independent Crossref signals instead of onlyrelation['is-retracted-by'](which hit 0 of 12 sampled withdrawn works, which is why the old checker found nothing), with the winning signal and the check time stored on the reference and the export gate reading only what was already stored; citation discovery in both directions on live data, where one English record reports 83,700 citing works and reads 50 of them while the result says so (incomplete), four Chinese-source records return 47–50 citations each, andlanguage: zhcatches only 2 of 200 rows so it is not the criterion; and a three-state reading status (unread / reading / read) that survives a restart, filters the list, and counts what the text cites but you have not finished — where "reading" counts as unfinished, since onlyreadis finished. Previously it made a framework selection stop being silently ignored: two hand-maintained whitelists listed three of four framework ids, socodebuddysurvived the settings parser and vanished on the next read, and session persistence dropped it too. and before that a windowless run used to stall where nobody could look, and the review that was supposed to audit it never started.start --no-openhas no window to click a permission card in: a real task sat after its first gated tool call with no further event for six minutes.GET /api/v1/permissionsandPOST /api/v1/permissions/:id/approve(purescience permissions/purescience approve) now read and answer those cards through the same narrow Task view (acp:get-state/acp:respond-permission, 11 → 13 commands — without them the route exists and answers 500), and approving is a named authority,satisfy-approval, which a generic task context cannot exercise: "who approved this" is never abstracted into "automation". Auto-review moved from the renderer to main, because a window was its only trigger — two identical CLI runs, the one with a window open reviewed, the other recorded nothing. Both paths converge through the existing already-reviewed check, so a turn is reviewed once. That first version of the trigger read the session once and so never fired at all — theend_turnevent lands before the session is written (~1.8 s later, measured), which left a finished CLI turn unaudited with a clean-looking log; it now waits out the write on a bounded schedule and reportsunreadablerather than dropping a turn silently (v2.15.3, measured before/after on the same CLI channel: 0 review rows → 1, verdictpass). Also in this version: a review persists outside any default five-second transaction budget, names its failures in seven families (the raw text kept as evidence), and stops at 300 tool calls per session; a client reset on a tunnel is no longer anuncaughtException; artifact finalisation waits out a durability race with a bounded retry instead of failing t...
PureScience v2.21.0
Maturity and Known Limitations
We label every capability the way we build it: ✅ shipped and verified on a real machine; 🚧 partly done, with the gap written next to it; 🗺️ planned. Each claim is traceable — a commit, a real-run log, or a record under docs/evidence/.
- ✅ Shipped and machine-verified: This version made what leaves the app come back with its own receipts, and made a long session readable. The same session exported from the window, the web surface and the CLI produced a byte-identical payload —
d36518b53a7dat 24 / 49 / 326 ms — with the whole-package digests differing only inexportedAt, because forging one timestamp to make three numbers agree would forge where the file came from; zip entries now carry a fixed 1980 epoch so the same content and stamp pack to the same bytes. An imported package recomputes its own claims: usage is derived fromconversation.jsonand compared field by field (state: match,turns: 1, with turns that recorded nothing counted apart), tool readings judge the manifest's declaration against the bytes it shipped (absent:no-tool-callsstays a named absence, never "0 tool calls checked"), the verdict is written into the import record and compared there, and a remote import is refused outright (401,local-only-authority) rather than quietly allowed. A settled turn keeps its own 24 px summary row with its own token total, and the three inputs stay apart — a number, "usage not recorded", or no line at all, never a 0 — while the row still folds: on a real window two turns producedgroups=2withexpanded: falsethentrue, and four turns producedsummaryRows:[24,24,24,24]in an 806 px viewport. That pair of readings took four attempts, and the reason is worth keeping: only one summary row ever appeared, and the cause was the deterministic test agent reusing the same tool-call ids every turn — the app correctly treated them as updates of the first turn's rows — so the product-side fix that shipped is the transcript merging the document's activity list with the live run's instead of letting the live run replace history. The CLI now serves an external MCP client: four read tools watched one audit line per call, then a write (archive_session) that resolves its target read-only, hangs the app's own approval card, and only then acts, refusing by name (not-approved,approval-unavailable,permission-gate-not-wired,already-archived) with the two ledgers paired by the sametoolCallId— cancelling leaves the row untouched, approving writesarchivedand the client reads it back (2 passed, 18.3 s). The home page gained five use-case cards whose every number and quote must appear verbatim in its own evidence file — a reading drifting by a tenth of a second turns that guard red — with a copy control answered by the main process's clipboard bridge rather than the window's denied one. DOCX tables are now drawn from what the document declares instead of what the renderer assumed: the table's six edges read fromtblBorders(falling back one level totblStyle), applied by rendered geometry, a cell's own rules winning, and a table that declares nothing left untouched — a 3 pt green double frame over a 0.5 pt grey grid measured 3.21 px and 0.80 px on a real window. Along the way a real defect came out of a red lane rather than from reading code: the egress proxy'sstop()waited forever when a client held its CONNECT tunnel open, whichcloseAllConnectionsdoes not cover. The previous version gave the wait a name and a price, and made the literature library check a retraction, follow citations both ways, and remember how far you read. Seven of the app's slowest requests were attributed from a week of its own log — 141 of 214 error/warn lines were slow IPC — before anything was changed.notebook:statere-parsed whole run documents on every read: on the real 60.1 MB file with 80 runs the hot median went 358.2 ms → 1.6 ms (cold 222.7 → 165.1) after an mtime+size cache that deliberately does not deep-copy, guarded by two tests that make the cache unable to lie.reviewer:get-for-sessionscanned two tables carrying only primary-key indexes behind an N+1 read and now has both indexes plus one query;notifications:get-snapshotqueued its reads behind writes and no longer does;artifacts:read-previewwas left alone on purpose, because an event-loop canary sampled at the same moment (261 ms against its 430 ms) accounts for most of it — rotation is reported rather than "fixed".sessions:save-sessionturned out to be two different slownesses: the 400 ms that dominated the samples was a database query in the post-write capture step (writeMs 3 ms,cpuShare 0), so it is skipped when no artifact version was finalised; the other half is a 60 MB document's write and rename (writeDiskMs 211,writeReplaceMs 176 ms) and is left at the I/O floor.settings:list-skillsis the honest non-result: the log says 662 ms median while a full in-process call takes 50.8 ms cold and 11.0 ms hot over 67 real skills, so parallel reads and an mtime+size cache landed (11.0 → 8.1 ms, plus askill list attributionline past 150 ms) and no target is claimed — without a baseline of the same size, "we improved it" would stand in for "it is fast enough". An event-loop stall used to end atno-cpu-in-process, which is a measurement and not a cause: every record now carriesgarbage-collection,fs-queue-saturated,cpu-in-process,unexplainedorunmeasured, withunexplainednaming the milliseconds it ruled out andunmeasuredsaying an instrument was missing — merging those two is how "nobody was watching" starts reading as "the machine was fine"; the GC observer and the thread-pool canary are started with the app rather than when a stall is noticed, and the 20 ms saturation threshold comes from 57 real canary samples (min 1 / median 24 / p90 180 / max 686 ms). The literature library grew three connected pieces: a retraction check reading four independent Crossref signals instead of onlyrelation['is-retracted-by'](which hit 0 of 12 sampled withdrawn works, which is why the old checker found nothing), with the winning signal and the check time stored on the reference and the export gate reading only what was already stored; citation discovery in both directions on live data, where one English record reports 83,700 citing works and reads 50 of them while the result says so (incomplete), four Chinese-source records return 47–50 citations each, andlanguage: zhcatches only 2 of 200 rows so it is not the criterion; and a three-state reading status (unread / reading / read) that survives a restart, filters the list, and counts what the text cites but you have not finished — where "reading" counts as unfinished, since onlyreadis finished. Previously it made a framework selection stop being silently ignored: two hand-maintained whitelists listed three of four framework ids, socodebuddysurvived the settings parser and vanished on the next read, and session persistence dropped it too. and before that a windowless run used to stall where nobody could look, and the review that was supposed to audit it never started.start --no-openhas no window to click a permission card in: a real task sat after its first gated tool call with no further event for six minutes.GET /api/v1/permissionsandPOST /api/v1/permissions/:id/approve(purescience permissions/purescience approve) now read and answer those cards through the same narrow Task view (acp:get-state/acp:respond-permission, 11 → 13 commands — without them the route exists and answers 500), and approving is a named authority,satisfy-approval, which a generic task context cannot exercise: "who approved this" is never abstracted into "automation". Auto-review moved from the renderer to main, because a window was its only trigger — two identical CLI runs, the one with a window open reviewed, the other recorded nothing. Both paths converge through the existing already-reviewed check, so a turn is reviewed once. That first version of the trigger read the session once and so never fired at all — theend_turnevent lands before the session is written (~1.8 s later, measured), which left a finished CLI turn unaudited with a clean-looking log; it now waits out the write on a bounded schedule and reportsunreadablerather than dropping a turn silently (v2.15.3, measured before/after on the same CLI channel: 0 review rows → 1, verdictpass). Also in this version: a review persists outside any default five-second transaction budget, names its failures in seven families (the raw text kept as evidence), and stops at 300 tool calls per session; a client reset on a tunnel is no longer anuncaughtException; artifact finalisation waits out a durability race with a bounded retry instead of failing the turn; a Session leftrunningby a dead process is closed as interrupted at startup, and a Session that died between "plan approved" and "no longer waiting" comes back idle instead of showing an approval that already happened; a notebook kernel now leads its own process group on POSIX, so teardown is onekill(-pgid)instead of a pid walk that misses whatever forked mid-teardown and cannot see a child launchd adopted (the old path reportedreaped: trueon that), with the group claim checked against the kernel's real pgid and a named log when it does not match; external-source verification no longer fails every call (an emptyresponse.urlfell through??intoInvalid URL:, and a redirect's three outcomes now have three answers); and the review locator no longer demands a hash the model had to guess —contentHashis optional, pinned to the frozen block, withexpected/receivednamed on a mismatch. Measured during the audit that produced this batch, on the 2.15.1 candidate build: a windowless review returningflaggedacross five checks, 26 real ...
PureScience v2.20.0
Maturity and Known Limitations
We label every capability the way we build it: ✅ shipped and verified on a real machine; 🚧 partly done, with the gap written next to it; 🗺️ planned. Each claim is traceable — a commit, a real-run log, or a record under docs/evidence/.
-
✅ Shipped and machine-verified: Where the wait goes now has a name and a price, and the literature library can check a retraction, follow citations both ways and remember how far you read. Seven of the app's slowest requests were attributed from a week of its own log — 141 of 214 error/warn lines were slow IPC — before anything was changed.
notebook:statere-parsed whole run documents on every read: on the real 60.1 MB file with 80 runs the hot median went 358.2 ms → 1.6 ms (cold 222.7 → 165.1) after an mtime+size cache that deliberately does not deep-copy, guarded by two tests that make the cache unable to lie.reviewer:get-for-sessionscanned two tables carrying only primary-key indexes behind an N+1 read and now has both indexes plus one query;notifications:get-snapshotqueued its reads behind writes and no longer does;artifacts:read-previewwas left alone on purpose, because an event-loop canary sampled at the same moment (261 ms against its 430 ms) accounts for most of it — rotation is reported rather than "fixed".sessions:save-sessionturned out to be two different slownesses: the 400 ms that dominated the samples was a database query in the post-write capture step (writeMs 3 ms,cpuShare 0), so it is skipped when no artifact version was finalised; the other half is a 60 MB document's write and rename (writeDiskMs 211,writeReplaceMs 176 ms) and is left at the I/O floor.settings:list-skillsis the honest non-result: the log says 662 ms median while a full in-process call takes 50.8 ms cold and 11.0 ms hot over 67 real skills, so parallel reads and an mtime+size cache landed (11.0 → 8.1 ms, plus askill list attributionline past 150 ms) and no target is claimed — without a baseline of the same size, "we improved it" would stand in for "it is fast enough". An event-loop stall used to end atno-cpu-in-process, which is a measurement and not a cause: every record now carriesgarbage-collection,fs-queue-saturated,cpu-in-process,unexplainedorunmeasured, withunexplainednaming the milliseconds it ruled out andunmeasuredsaying an instrument was missing — merging those two is how "nobody was watching" starts reading as "the machine was fine"; the GC observer and the thread-pool canary are started with the app rather than when a stall is noticed, and the 20 ms saturation threshold comes from 57 real canary samples (min 1 / median 24 / p90 180 / max 686 ms). The literature library grew three connected pieces: a retraction check reading four independent Crossref signals instead of onlyrelation['is-retracted-by'](which hit 0 of 12 sampled withdrawn works, which is why the old checker found nothing), with the winning signal and the check time stored on the reference and the export gate reading only what was already stored; citation discovery in both directions on live data, where one English record reports 83,700 citing works and reads 50 of them while the result says so (incomplete), four Chinese-source records return 47–50 citations each, andlanguage: zhcatches only 2 of 200 rows so it is not the criterion; and a three-state reading status (unread / reading / read) that survives a restart, filters the list, and counts what the text cites but you have not finished — where "reading" counts as unfinished, since onlyreadis finished. Previously it made a framework selection stop being silently ignored: two hand-maintained whitelists listed three of four framework ids, socodebuddysurvived the settings parser and vanished on the next read, and session persistence dropped it too. and before that a windowless run used to stall where nobody could look, and the review that was supposed to audit it never started.start --no-openhas no window to click a permission card in: a real task sat after its first gated tool call with no further event for six minutes.GET /api/v1/permissionsandPOST /api/v1/permissions/:id/approve(purescience permissions/purescience approve) now read and answer those cards through the same narrow Task view (acp:get-state/acp:respond-permission, 11 → 13 commands — without them the route exists and answers 500), and approving is a named authority,satisfy-approval, which a generic task context cannot exercise: "who approved this" is never abstracted into "automation". Auto-review moved from the renderer to main, because a window was its only trigger — two identical CLI runs, the one with a window open reviewed, the other recorded nothing. Both paths converge through the existing already-reviewed check, so a turn is reviewed once. That first version of the trigger read the session once and so never fired at all — theend_turnevent lands before the session is written (~1.8 s later, measured), which left a finished CLI turn unaudited with a clean-looking log; it now waits out the write on a bounded schedule and reportsunreadablerather than dropping a turn silently (v2.15.3, measured before/after on the same CLI channel: 0 review rows → 1, verdictpass). Also in this version: a review persists outside any default five-second transaction budget, names its failures in seven families (the raw text kept as evidence), and stops at 300 tool calls per session; a client reset on a tunnel is no longer anuncaughtException; artifact finalisation waits out a durability race with a bounded retry instead of failing the turn; a Session leftrunningby a dead process is closed as interrupted at startup, and a Session that died between "plan approved" and "no longer waiting" comes back idle instead of showing an approval that already happened; a notebook kernel now leads its own process group on POSIX, so teardown is onekill(-pgid)instead of a pid walk that misses whatever forked mid-teardown and cannot see a child launchd adopted (the old path reportedreaped: trueon that), with the group claim checked against the kernel's real pgid and a named log when it does not match; external-source verification no longer fails every call (an emptyresponse.urlfell through??intoInvalid URL:, and a redirect's three outcomes now have three answers); and the review locator no longer demands a hash the model had to guess —contentHashis optional, pinned to the frozen block, withexpected/receivednamed on a mismatch. Measured during the audit that produced this batch, on the 2.15.1 candidate build: a windowless review returningflaggedacross five checks, 26 real approvals in the same run (PubMed / DOI / Wayback), three livefetch_sourcefetches (6,988 / 7,018 / 5,403 characters), and that runcompletedwith its artifact written. Re-checked on the build of this commit: the app boots headless in 3 s andGET /api/v1/permissionsanswers200 {"data":[]}with no window open — the failure that replaced was a 500 from that same route. The performance conclusion was wrong and the correction is the reading: the 118.9 s save peak and the 203 s projection panel were one process-level freeze, caught by an event-loop stall watchdog that samples from a second thread while the loop is stuck — withdrawn, not optimised around. Not done here: the read-only expression for built-in specialists. -
✅ Shipped and machine-verified: Batch removal is one write now: all of the ticked rows go, or none do. The previous version looped the single removal and said what that cost — a crash halfway could leave half the rows gone, and copy pruning decided row by row would depend on list order. This writes the register once and judges pruning against the sources that remain, so two sources sharing bytes clean up once and a surviving source never loses its copy. The change also changes what a refusal means, and the copy had to follow: an atomic batch that fails was not partly applied, so the sentence says the batch was not applied and nothing was removed — and the old "removed 3, 1 refused" string is deleted from all nine dictionaries, because a key describing a failure that can no longer happen is worse than no key. The armed-then-confirm step and page-scoped select-all stay: they guard against a human misclick, which has nothing to do with whether the batch is a loop. Measured: three registered, two ticked, armed, confirmed —
1 passed (7.4s), the unticked row still there. Sources can be removed in batches, without a new channel. One batch is N runs of the single removal, so copy pruning by reference and the verbatim refusal of a rejected row keep working exactly as they always did — a batch cannot become a shortcut around them. Three restraints: the first click only arms the action and a second confirms it, because this is the one control whose effect cannot be undone from the screen; select-all covers only the current page, since ticking rows nobody has looked at would decide on their behalf; and a row the store refuses stays where it is, with the reason shown verbatim. Measured: three registered, two ticked, armed and confirmed —1 passed (5.4s), and the unticked source is still there. The trade this version made and names: a batch is not atomic, because it is N writes, so a crash halfway could leave some rows gone and some not. An atomic alternative (one write, pruning judged against what remains) was built and not adopted; the choice is recorded in the evidence file. Not done: no undo, paging is still client-side. Molecular dynamics on CPU, and an honest answer about reproducibility. GROMACS 2025.4 from conda-forge minimises crambin and runs 10 ps in seconds: 11 frames, 642 atoms, 303 K measured against a 300 K target, mean potential −2859.6 kJ/mol. The important finding is a negative one — the...