Close in-bar 1.0 security, lifecycle, and operator gaps - #120
Conversation
Align the 1.0 bar to one local Windows endpoint, then implement the sequenced waves: capability audit, native tray and quit-flush, conversation-scoped streams, observability, endpoint self-test, operator runbook, updater install UX, and Windows debug-exe runtime smoke.
There was a problem hiding this comment.
🟡 Changes recommended
Resolve the native-cache hydration and endpoint self-test correctness and timeout issues before approval.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This PR hardens the Windows-focused 1.0 release across lifecycle, security, diagnostics, packaging, CI, and documentation.
Changes:
- Adds native tray, quit-flush, relaunch, and conversation-scoped streaming.
- Adds endpoint self-tests, health telemetry, runtime warnings, and updater UX.
- Tightens capabilities, CI caching/smoke tests, and release documentation.
File summaries
| File | Description |
|---|---|
vite.config.js |
Updates coverage modules. |
src/routes/+page.svelte |
Integrates lifecycle, diagnostics, streaming, and updater UX. |
src/lib/security-boundary.test.ts |
Adds security boundary tests. |
src/lib/sdk.ts |
Adds lifecycle and health APIs. |
src/lib/runtime-capability.test.ts |
Updates capability tests. |
src/lib/operation-outcome.ts |
Classifies health queries. |
src/lib/ipc-deadlines.ts |
Adds query deadlines. |
src/lib/ipc-contracts.ts |
Adds health-ring IPC support. |
src/lib/integrations.ts |
Adds integration metadata. |
src/lib/endpoint-self-test.ts |
Implements endpoint checks. |
src/lib/endpoint-self-test.test.ts |
Tests endpoint checks. |
src/lib/conversation-session.ts |
Adds archived message patching. |
src/lib/conversation-session.test.ts |
Tests message patching. |
src-tauri/src/tray.rs |
Adds native tray behavior. |
src-tauri/src/runtime_manager.rs |
Exposes runtime phase. |
src-tauri/src/quit_flush.rs |
Implements quit flushing. |
src-tauri/src/lib.rs |
Registers lifecycle and smoke commands. |
src-tauri/Cargo.toml |
Removes opener dependency. |
src-tauri/Cargo.lock |
Updates Rust dependencies. |
src-tauri/capabilities/default.json |
Prunes renderer permissions. |
sidecar/health-ring.test.ts |
Tests health history. |
sidecar/health-ring.js |
Adds bounded health history. |
sidecar/gateway.test.ts |
Tests route classification. |
sidecar/gateway.js |
Adds metadata-only access logging. |
sidecar/foundry-sidecar.js |
Integrates health and runtime telemetry. |
sidecar/foundry-runtime-pin.test.ts |
Tests runtime pin warnings. |
sidecar/foundry-runtime-pin.js |
Defines runtime pin warnings. |
scripts/smoke-packaged-runtime.cjs |
Adds packaged runtime smoke tests. |
scripts/hydrate-foundry-native.cjs |
Handles native cache hydration. |
RELEASE_ROADMAP.md |
Narrows the 1.0 release bar. |
README.md |
Updates limitations and documentation. |
package.json |
Pins the SDK and adds scripts. |
package-lock.json |
Updates dependency lock data. |
FLINT_DESIGN_SPEC.md |
Updates SDK pin guidance. |
docs/USER_GUIDE.md |
Updates bundled Node guidance. |
docs/RELEASE.md |
Documents updater rollback. |
docs/README.md |
Indexes operator documentation. |
docs/PRODUCT_PLAN.md |
Defines implementation waves. |
docs/LINUX_BUILD_PLAN.md |
Clarifies Linux deferral. |
docs/BACKLOG.md |
Updates deferred work. |
docs/ADMIN.md |
Adds the operator runbook. |
CONTRIBUTING.md |
Updates planning guidance. |
AGENTS.md |
Updates the documentation map. |
.gitignore |
Ignores native cache contents. |
.github/workflows/release.yml |
Adds release native caching. |
.github/workflows/ci.yml |
Adds CI caching and Windows smoke. |
.github/copilot-instructions.md |
Updates project guidance. |
.changeset/wave-5-7-followthrough.md |
Records endpoint and smoke changes. |
.changeset/wave-3-8-progress.md |
Records lifecycle and observability changes. |
.changeset/wave-2-quit-flush-stream.md |
Records quit and stream changes. |
.changeset/wave-2-native-tray.md |
Records native tray changes. |
.changeset/wave-1-security-ci.md |
Records security and CI changes. |
.changeset/backlog-1-0-gaps.md |
Updates planning scope. |
Review details
Suppressed comments (4)
scripts/hydrate-foundry-native.cjs:43
- The cache restore is placed in the root
preinstallhook, butnpm ciremovesnode_modulesbefore running lifecycle scripts. At this pointpath.dirname(DEST_DIR)(node_modules/foundry-local-sdk) does not exist, so this guard returns and the cached native payload is never restored; the SDK install then still has to download from NuGet. Move hydration to a lifecycle point after the dependency directory is materialized, or restore through a staging path consumed by the SDK install script.
function restore() {
if (!dirHasFiles(CACHE_DIR)) return;
if (!fs.existsSync(path.dirname(DEST_DIR))) return;
copyDir(CACHE_DIR, DEST_DIR);
src/lib/endpoint-self-test.ts:129
- This chooses the selected alias before the ID returned by
/v1/models, so the check can pass even when the gateway cannot route a model ID from its own envelope (and it can test an alias that is not listed at all). Since this self-test is explicitly meant to verify “a returned ID round-trips into chat,” select a returneddata[].idhere, or block when the models envelope did not provide one.
src/lib/endpoint-self-test.ts:197 hasDeltaonly detects any JSON object in an SSEdata:line, so an empty delta/role-only event plus[DONE]is reported as “delivers a token.” This can mark streaming as verified without receiving content; parse the SSE choices and require a non-empty token delta before passing.
src/routes/+page.svelte:4966- When a stream is no longer visible, every token calls
saveConversations(), which synchronously rewrites the whole localStorage archive. Long generations will therefore perform a blocking storage write per token, causing UI/event-loop stalls and unnecessary quota/retry pressure. Batch or debounce archive patches and persist on a bounded cadence/finalization instead.
- Files reviewed: 50/53 changed files
- Comments generated: 3
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: joelst <30506169+joelst@users.noreply.github.com>
Co-authored-by: joelst <30506169+joelst@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Moderate unresolved issues remain in cache restoration, tray/quit handling, and endpoint self-test correctness and timeouts.
Review details
Suppressed comments (11)
Previously missed (1) — in code that hasn't changed since the last review.
src/lib/endpoint-self-test.ts:254
- The disconnect check aborts immediately after starting
fetch, before waiting for headers or any streamed bytes. That can pass solely because the client cancelled a request before it was dispatched, so it does not exercise gateway disconnect handling or establish anything about native generation cancellation. Start a real streaming response, then abort while its body is in flight (and assert the request reached the endpoint) before reporting this check as passed.
package.json:19
- This cache restore runs from the root
preinstallhook, whilenpm ciremoves/recreatesnode_modulesand thefoundry-local-sdkpackage (and its install script) has not been installed yet. Copying intonode_modules/foundry-local-sdk/foundry-local-coreat this point can therefore be deleted by dependency installation, so the SDK still downloads from NuGet on cache hits. Restore after the package is materialized, or integrate the cache with the SDK's installer, and add a CI assertion that a cache hit avoids the download.
"preinstall": "node scripts/hydrate-foundry-native.cjs --restore",
scripts/hydrate-foundry-native.cjs:42
npm ciremovesnode_modulesbefore running the rootpreinstalllifecycle, sonode_modules/foundry-local-sdk(and its parent) does not exist yet. This guard therefore returns on every clean install and the restored cache is never copied before the SDK's install script runs, defeating the CI/release cache. Create the destination parent (or remove this guard) instead of treating its absence as a reason to skip restore.
if (!fs.existsSync(path.dirname(DEST_DIR))) return;
src-tauri/src/lib.rs:49
- If native tray creation fails, this only logs the error while the frontend still defaults
keepServiceInBackgroundto true and prevents window close. Since the frontend tray was removed, a tray initialization failure can leave a hidden window with no Open/Quit recovery control. Fail startup or disable close-to-tray whentray::installfails.
src-tauri/src/tray.rs:34 AppHandle::exitis the immediate-exit API, so the native tray Quit path bypasses theExitRequestedevent that emitsflint-quit-flush. When the renderer is alive, this can terminate without flushing conversations; when it is dead, it is fine to fall back to the timeout path. Request an exit through the event-driven API here (with the existing timeout handling) rather than calling immediateexitdirectly.
src/lib/endpoint-self-test.ts:304- This branch treats any response containing
tool_callsas a pass, even when the gateway returned an HTTP error (for example, a 500 JSON error that happens to include that field). Checkres.okbefore accepting the response, as the chat check does, so Flint-verified cannot be granted for a failed request.
src/lib/endpoint-self-test.ts:229 hasDeltaonly checks that an SSEdata:payload contains{; it does not verify that any chunk contains a token/content delta. A stream with only role/metadata chunks plus[DONE]will be marked as delivering a token. Parse the SSE data and require a non-empty content delta (or another explicit first-token signal) before passing this check.
src/lib/endpoint-self-test.ts:187usageSeenis set from any JSON body before checkingres.okor the response shape. A failed HTTP response containing ausagefield could therefore pass the usage check even though no completion succeeded. Only record usage after accepting a successful completion response.
src/lib/endpoint-self-test.ts:159- When no alias is selected, this chooses the first cached
/v1/modelsentry, but that endpoint can contain vision/STT models as well as chat models. The self-test then reports chat/stream failures instead of the promised blocked state when no cached chat model is available. Restrict the candidate to the selected chat-capable catalog model (or block when there is none) rather than treating an arbitrary cached model as a chat target.
src/lib/endpoint-self-test.ts:159 - When a model is selected, this bypasses the ID returned by
/v1/models, so the self-test can pass for a stale/unlisted alias and never verifies the documented “returned ID round-trips into chat” behavior. Select the returned model ID (or explicitly fail when the requested model is not present) before issuing the chat requests.
src/lib/endpoint-self-test.ts:130 - These direct
fetchcalls have no timeout or cancellation budget. If the local endpoint accepts a connection but stalls on headers or a response body, the Diagnostics button remains busy indefinitely and the later checks never run. Use a bounded per-check/overall deadline and abort the active request when it expires.
- Files reviewed: 51/54 changed files
- Comments generated: 0 new
- Review effort level: Lite
|
Follow-on: this stack lands as 0.9.0, the first stable (skip 0.8.0), so we can prove the in-app updater from 0.7.0 evaluation before 1.0.0. Version bump and embeddings path are on #121. Do not tag 1.0.0 as the first |
|
Addressed the Copilot review on the stack tip (#121, Fixed
Not changed
|
There was a problem hiding this comment.
🟡 Changes recommended
Smoke failures can be reported as successful, and the native cache restore runs too late to protect dependency installation.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 51/54 changed files
- Comments generated: 3
- Review effort level: Balanced
| */ | ||
| export function classifyGatewayRoute (urlPath) { | ||
| const path = String(urlPath || '').split('?')[0]; | ||
| if (path.includes('/chat/completions')) return 'chat'; |
There was a problem hiding this comment.
Agreed. classifyGatewayRoute now matches chat/completions, embeddings, and models as path segments (/(^|\/)chat\/completions(\/|$)/ and the same shape for the others). /v1/chat/completions-evil is other. Landed on #121 (c70b086).
Summary
1.0 was blocked by an inconsistent bar, not a missing feature list. This PR tightens the bar to one local Windows endpoint (macOS evaluation-only; no multi-endpoint manager) and implements the sequenced in-bar waves so the remaining work is process, not unowned code.
Final rubber-duck: do not date or cut 1.0.0 from this PR. Code closes the implementable 1.0 criteria. Ship still needs a signed clean-machine dogfood, recorded Continue/Cline/OpenClaw version pins, a packaged UX walk, and a stable 1.0.0 publish so
releases/latestresolves.What this does
RELEASE_ROADMAP.md,PRODUCT_PLAN.md,BACKLOG.mdsecurity-boundarysuite, exact SDK 1.2.4 pin + warn, CI Foundry-native cacheExitRequested; streams follow conversation idtray.rs,quit_flush.rs,applyMessagePatchonAccess,getHealthRing, Monitor TTFT/tok/sdocs/ADMIN.md; Node troubleshooting matches bundled runtimeFLINT_RUNTIME_SMOKE=1/npm run smoke:runtimeon Windows CIStill process (not this PR)
channel=stable(not a prerelease)Risks the duck did not paper over
npm run checkstill reports pre-existingsidecar/byom-import.jsTS errors (untouched here).cargo checkwas not run in this environment (cl.exemissing); CI compiles the new Rust.Flint seam
endpoint-self-test.ts,conversation-session.ts(applyMessagePatch)sdk.tsquit/relaunch/flush subscribe,getHealthRinggetHealthRing, gatewayonAccess, runtime pin warn, health ringquit_app/relaunch_app, smoke wait-for-readyHotspots:
src/routes/+page.svelte,src/lib/sdk.ts,src/lib/ipc-contracts.ts,sidecar/foundry-sidecar.js.New IPC command:
getHealthRing(query, 10s deadline).Safety and compatibility
getHealthRing; nativequit_app,ack_quit_flush,relaunch_app.usage/tool_callsis not-verified.$RESOURCErenderer read scope removed;$RESOURCEwrite deny kept; opener plugin removed; updater download-and-install kept for Wave 8.Validation
npm run check— 8 pre-existingbyom-import.jsdiagnostics; no new errors in files this PR ownsnpm run test:coverage(1130 tests, lines 97.06%)npm run verify:ipc-contracts(36 commands)npm run verify:markdown-links(151 links)npm run verify:bundle— not re-run here (resource map unchanged; sidecar/SDK still packaged as before)git diff --check(README trailing spaces from this work stripped)Handoff
verifiedwithout a recorded run andtestedWith.releases/latest/download/latest.json).smoke:runtimeis not a substitute for loading a model.