Repository navigation
feat(update): inert electron-updater 6.8.9 adapter for the draw.io proof (import-time surface, recorded no-op setFeedURL, honest check outcome) #491
Description
Activity
- addedelectron-compatElectron compatibility program areaElectron compatibility program areacompat:tier-2Electron compat Tier 2 (arch 04 §4)Electron compat Tier 2 (arch 04 §4)needs-specNeeds an approved docs/agents/spec-template.md spec before implementationNeeds an approved docs/agents/spec-template.md spec before implementation
on Oct 6, 2026 - addedmilestone:first-proofNeeded for the first migration proof (drawio-desktop on macOS, explicit legacy profile)Needed for the first migration proof (drawio-desktop on macOS, explicit legacy profile)
on Oct 6, 2026 Generated by an AI agent (Claude Code, Fable 5.1) on behalf of @0monish, 2026-10-06, under the map's execution doctrine. Statements are labelled FACT / INFERENCE / UNKNOWN by their author; nothing here authorizes implementation.
Doctrine audit findings for this issue
- Edge: F09-A2 (Who owns the compiled-in feed base (KEL-53/KEL-19 amendment) that autoUpdater.getFeedURL reports and setFeedURL is compared against? #409) (compiled-in feed base owner) gates the whole ticket, but the first-proof slice (inert electron-updater adapter with recorded setFeedURL) reads no feed. → Fix: Split the adapter slice out with no F09-A2 (Who owns the compiled-in feed base (KEL-53/KEL-19 amendment) that autoUpdater.getFeedURL reports and setFeedURL is compared against? #409) edge.
- Hidden coupling (with PANEL-D14 (Updater handling for the draw.io proof #434), X02-T3 (task(migrate): hand-authored draw.io first-proof artefact — keld.config.ts, literal-only keld.permissions.jsonc and package-manifest edits under the X02-T1 contract #499), F01-T4 (feat(app): host-minted session facts at spawn for synchronous app getters (getPath, getVersion, getName/name, getAppPath, getLocale, isPackaged) and the declared role environment — legacy path values #452)): Updater disablement depends on an environment variable: PANEL-D14 (Updater handling for the draw.io proof #434) has migrate write DRAWIO_DISABLE_UPDATE=true into 'the host-declared role environment', read at module top level (:75). That is a boot fact delivered at spawn (F01-T4 (feat(app): host-minted session facts at spawn for synchronous app getters (getPath, getVersion, getName/name, getAppPath, getLocale, isPackaged) and the declared role environment — legacy path values #452) / KEL-75 no-inherited-env) and a migrate output not present in X02-T3 (task(migrate): hand-authored draw.io first-proof artefact — keld.config.ts, literal-only keld.permissions.jsonc and package-manifest edits under the X02-T1 contract #499)'s file list. If it is absent, the boot-time checkForUpdates runs (:2092-2100) and F09-T1 (feat(update): inert electron-updater 6.8.9 adapter for the draw.io proof (import-time surface, recorded no-op setFeedURL, honest check outcome) #491)'s 'typed error on explicit check' fires at activation. → Fix: Name the declared-role-environment field and its schema owner; add it to X02-T3 (task(migrate): hand-authored draw.io first-proof artefact — keld.config.ts, literal-only keld.permissions.jsonc and package-manifest edits under the X02-T1 contract #499) outputs and to F01-T4 (feat(app): host-minted session facts at spawn for synchronous app getters (getPath, getVersion, getName/name, getAppPath, getLocale, isPackaged) and the declared role environment — legacy path values #452)'s payload; F09-T1 (feat(update): inert electron-updater 6.8.9 adapter for the draw.io proof (import-time surface, recorded no-op setFeedURL, honest check outcome) #491) AC covers both env-present and env-absent boots.
YAGNI classification:
first-proof— Slice only: an inert electron-updater adapter — autoUpdater.logger.transports., autoDownload, autoInstallOnAppQuit are set unconditionally at import (:82-86), on('error') is registered at top level (:2233) and setFeedURL runs outside the disable guard (:2078). Park the KEL-53 state machine, update. guarded calls and quitAndInstall.Disposition: each finding stays open on this issue until its fix is applied to the body or refuted with evidence.
- changed the title
[-]feat(update): autoUpdater core facade + electron-updater shim over the KEL-53 state machine with recorded no-op setFeedURL and info-only events[/-][+]feat(update): inert electron-updater 6.8.9 adapter for the draw.io proof (import-time surface, recorded no-op setFeedURL, honest check outcome)[/+]on Oct 6, 2026 - addedenhancementNew feature or requestNew feature or requestneeds-infoWaiting on more informationWaiting on more information
on Oct 6, 2026 This was generated by AI during triage.
Triage Notes
What we've established so far:
- The slice is specified in the issue body (what to build, binary acceptance criteria, negative controls, ownership and gates).
- Milestone
first-proof; tracker of record as stated in the body.
What we still need from you (@0monish):
- Resolve PANEL-P3 (decided 2026-10-07): draw.io first proof is config + 1 edit under legacy #420
PANEL-P3— S2 draw.io boot and authority trace (E1): run its main under Bun 1.4.2 with a throwing @keld/electron stub and a Node-API tracer over install + activation + 4-step primary workflow. Its answer can change this ticket's acceptance criteria.
Once those resolve, this ticket moves to
ready-for-agentwith an Agent Brief.This was generated by AI during triage.
Agent Brief
Category: enhancement
Summary: feat(update): inert electron-updater 6.8.9 adapter for the draw.io proof (import-time surface, recorded no-op setFeedURL, honest check outcome)Current behavior:
No updater surface exists: @keld/electron exports onlyapp, and no package in the workspace answers theelectron-updaterspecifier (verified at origin/main 29a4cd7).Desired behavior:
Build a Keld-owned package that stands in for theelectron-updaterspecifier. Migrate swaps it in as a dependency replacement in the app manifest: X02-T3 owns that swap, under the single-resolver rule closed in X02-A1. The package exposes the AppUpdater subset that draw.io touches, as listed by the PANEL-P3 trace. It must evaluate at import with no host frames and no network or filesystem I/O, and must never fabricate an updater outcome.Behaviour, mirroring electron-updater 6.8.9 wherever the upstream shape can be honoured without a host updater:
- A default export carrying
autoUpdater, which is an EventEmitter. - A
loggeraccessor that stores and returns the identical object; null yields a no-op logger. autoDownload,autoInstallOnAppQuitandautoRunAppAfterInstallare plain booleans with no side effect.setFeedURL(string or options)returns undefined. It never throws, constructs no client, and stores nothing any host path reads. This is a ▲: the feed is host-declared and the app feed is ignored.getFeedURL()returns upstream's deprecated literal.checkForUpdates()mirrors upstream's packaged gate and reads app.isPackaged from F01-T4. When not packaged it resolves null with no event. When packaged and no host updater exists, it emits exactly oneerrorcarrying a newly allocated registry code and rejects the same promise.downloadUpdate()before any update is offered rejects, as upstream's "check first" does.quitAndInstall()with nothing staged emits oneerrorand never quits.- Never emitted:
update-available,update-not-available,download-progress,update-downloaded,checking-for-update.
First deliverable: a spec from
docs/agents/spec-template.mdas a PR, stopped for human approval; implement only afterStatus: approved.Key interfaces:
- The Keld-owned adapter package; @keld/electron app.isPackaged (F01-T4); the migrate dependency replacement (X02-T3);
- F09-T3 conformance cells in the KEL-237 corpus-runner pattern.
Acceptance criteria:
- Every F09-T3 match cell flips from its recorded expected verdict to pass in this change, and every F09-T3 ▲ cell carries its divergence verdict; no F09-T3 cell is skipped, retried or left silently red
- Under Bun 1.4.2, the default-import form followed by destructuring
autoUpdateryields an EventEmitter, and the app-link frame count attributable to the adapter during import is 0 - Assigning an object to
loggerand reading it back returns the identical object (strict equality), so property writes made through it land on the assigned logger -
setFeedURLcalled with draw.io's github provider options and with a plain string returns undefined both times, throws nothing, opens no socket and writes no file - A fixture that replays the PANEL-P3-recorded draw.io updater call sequence with
DRAWIO_DISABLE_UPDATE=trueset explicitly in the role environment makes zerocheckForUpdatescalls and receives zeroerrorevents - The same replay with the variable absent and app.isPackaged false makes
checkForUpdatesresolve null with zero events - With the variable absent, app.isPackaged true and no host updater,
checkForUpdatesemits exactly oneerrorwhose code is a registry-allocated code outside KELD-COMPAT-001..009 and KELD-UPDATE-*, and the returned promise rejects - Across every case above, no
update-not-available,update-availableorupdate-downloadedevent is ever emitted -
quitAndInstall()with nothing staged emits exactly oneerrorand app.quit is never called - Negative control: Making setFeedURL throw fails the setFeedURL no-throw cell and the replay fixture (draw.io's safeUpdaterCall would open its Update Error dialog)
- Negative control: Returning a fresh wrapper from the logger getter fails the identity cell
- Negative control: Emitting update-not-available from checkForUpdates with no host updater fails the never-fabricate cell
- Negative control: Removing the isPackaged gate makes the unpackaged replay emit an error and fails the unpackaged-check cell
- Negative control: Calling app.quit from quitAndInstall with nothing staged fails the no-quit cell
- Negative control: Reusing a KELD-UPDATE-* code for the no-host-updater error fails the code-provenance assertion
Out of scope:
- Any host update channel, feed contact, download, staging or activation (F09-T5); the Electron-core autoUpdater facade (F09-T4); feed-base reporting (F09-A2 closed: parked under KEL-53 / arch 06 §4a); comparison of the setFeedURL argument with a build config (no schema and no consumer); declaring or delivering the role environment variable (F01-T4).
Tracker of record and claim location: see "Ownership and gates" in the issue body.
- A default export carrying
Parent
Epic #490 · Map #391 · Unit F09 · Kind:
feature· Tier: Tier 2 · Maturity target: L0 · Size: S · Milestone:first-proofWhat to build
Build a Keld-owned package that stands in for the
electron-updaterspecifier. Migrate swaps it in as a dependency replacement in the app manifest: X02-T3 owns that swap, under the single-resolver rule closed in X02-A1. The package exposes the AppUpdater subset that draw.io touches, as listed by the PANEL-P3 trace. It must evaluate at import with no host frames and no network or filesystem I/O, and must never fabricate an updater outcome.Behaviour, mirroring electron-updater 6.8.9 wherever the upstream shape can be honoured without a host updater:
autoUpdater, which is an EventEmitter.loggeraccessor that stores and returns the identical object; null yields a no-op logger.autoDownload,autoInstallOnAppQuitandautoRunAppAfterInstallare plain booleans with no side effect.setFeedURL(string or options)returns undefined. It never throws, constructs no client, and stores nothing any host path reads. This is a ▲: the feed is host-declared and the app feed is ignored.getFeedURL()returns upstream's deprecated literal.checkForUpdates()mirrors upstream's packaged gate and reads app.isPackaged from F01-T4. When not packaged it resolves null with no event. When packaged and no host updater exists, it emits exactly oneerrorcarrying a newly allocated registry code and rejects the same promise.downloadUpdate()before any update is offered rejects, as upstream's "check first" does.quitAndInstall()with nothing staged emits oneerrorand never quits.update-available,update-not-available,download-progress,update-downloaded,checking-for-update.Spec gate: the first deliverable is a spec from
docs/agents/spec-template.md, opened as a PR and stopped for human approval; implementation starts only atStatus: approved.Acceptance criteria
autoUpdateryields an EventEmitter, and the app-link frame count attributable to the adapter during import is 0loggerand reading it back returns the identical object (strict equality), so property writes made through it land on the assigned loggersetFeedURLcalled with draw.io's github provider options and with a plain string returns undefined both times, throws nothing, opens no socket and writes no fileDRAWIO_DISABLE_UPDATE=trueset explicitly in the role environment makes zerocheckForUpdatescalls and receives zeroerroreventscheckForUpdatesresolve null with zero eventscheckForUpdatesemits exactly oneerrorwhose code is a registry-allocated code outside KELD-COMPAT-001..009 and KELD-UPDATE-*, and the returned promise rejectsupdate-not-available,update-availableorupdate-downloadedevent is ever emittedquitAndInstall()with nothing staged emits exactly oneerrorand app.quit is never calledNegative controls (each names the one mutation that must fail)
External predecessors (outside this tracker)
## Agent claimcomment on GitHub feat(update): inert electron-updater 6.8.9 adapter for the draw.io proof (import-time surface, recorded no-op setFeedURL, honest check outcome) #491, the tracker of recordOwnership and gates
docs/agents/workflow.md§ Tracker issue (rule lands with docs(agents): record the #517 tracker-of-record rule in root AGENTS.md and the workflow claim protocol #520). Linear reference: KEL-53 (In Progress) carries a link to this issue.The authoritative agent contract is the latest Agent Brief comment on this issue (triage skill); this body is the planning record.
Out of scope
Any host update channel, feed contact, download, staging or activation (F09-T5); the Electron-core autoUpdater facade (F09-T4); feed-base reporting (F09-A2 closed: parked under KEL-53 / arch 06 §4a); comparison of the setFeedURL argument with a build config (no schema and no consumer); declaring or delivering the role environment variable (F01-T4).
Notes
Adjacent owners (one owner per atom):
DRAWIO_DISABLE_UPDATE=trueinto that field: X02-T3, the migrate output.FACT (keld-runtime at origin/main 29a4cd7): RoleConfig already sets explicit per-generation environment keys and removals. That is a delivery primitive F01-T4 can reuse, not the schema owner.
INFERENCE: delivering this variable as an environment key is consistent with the KEL-75 spec. That spec keeps KELD_APP_LINK as the sole bootstrap variable and forbids grant exceptions arising from an environment value, and the declared variable carries no authority.
INFERENCE (keld-runtime read at b4b907c): the macOS non-strict spawn path inherits the operator environment. The replay fixture therefore sets or clears the variable explicitly and never relies on the shell.
UNKNOWN: whether the first-proof activation cell runs with isPackaged false (
keld dev) or true (installed root). It is decided by F01-T4 and X02-T4, which is why both cases are covered.If the variable is absent and the app is packaged, draw.io's Update Error dialog appears at boot. That is the honest outcome, and it would fail the activation cell, so the env declaration is a product-cell dependency, not an adapter one.
Verification SHA for the current-behaviour facts: origin/main 29a4cd7 (2026-10-07).
Change log (doctrine-audit repair, 2026-10-07)