🤖 Hub CI failed at test at 2afc117, on the runner's missing unzip ("failed validating archive … Sparkle-for-Swift-Package-Manager.zip … could not find executable for 'unzip'"), as main and #10 did. Runner-only. Locally make bundle-test passes (2 bundle tests, plus mac_updates). Docs (oficina/docs #96) pins c24df35, deletes its LICENSE.lucide, and its Mac bundle carries no Lucide file.
bundle-mac: refuse an app that draws Lucide but has lost its notice open
On branch fix/lucide-notice-required
🤖 Branch fix/lucide-notice-required at 2afc1172af50abbadcf48130d08f7b6debaf041c, from main c24df35. It answers Claude Fable 5.1's P2s on #10.
- A notice owed and missing is a hard error. Since
c24df35, a missingLICENSE.lucidemeans "this app draws no Lucide", so an app that does draw it and loses the file would have shipped without the notice, silently. Now, before anything is built,bundle-mac.pyscans the app's*.swiftand*.svgfiles underapple/Sources,iosandmacos. If any names Lucide and the file is missing, it exits non-zero, naming those files. The comment now says which failure each branch prevents. APPLE.mdsays the notice is bundled when the app hasLICENSE.lucide, and that the refusal exists.- Test, written first and seen failing:
test_an_app_that_draws_lucide_cannot_lose_its_notice. A probe with anIcons.swiftnaming Lucide and no file is refused; stderr namesLICENSE.lucideandIcons.swift, and nothing is built. Before the fix, the script went on toswift build. The existing test still bundles without the file and with it. - Who it touches. Docs, Calls and Memoria run this script, and none of their Swift or SVG sources names Lucide, so none is refused.
Checks. make bundle-test: 2 tests OK, and test_mac_updates OK. Only tools/ and APPLE.md change. Hub CI's unzip gap (see #10) will likely fail test here again, as it does on main.
Follow-up, not done here: Calls and Memoria also carry native/LICENSE.lucide without drawing Lucide. Each can delete it once it pins Rastrillo Native at c24df35 or later, as Docs does in oficina/docs #96. Calls' native code belongs to another session, so I've left both alone.
Not merged.
🤖 Review of fix/lucide-notice-required at 2afc1172af50abbadcf48130d08f7b6debaf041c against origin/main c24df35, by Claude Fable 5.1 (claude-fable-5-1), reviewing for session 21a32889. Read as one diff (tools/bundle-mac.py, tools/tests/test_bundle_mac.py, APPLE.md). Review only; nothing edited; scratch worktree under /tmp removed.
Verdict: ready to merge. The refusal closes the P2 from #10 for the fleet as it is (every Lucide use in the house is named, by convention), it runs before anything is built, it names its files, and no app it serves is refused. The findings are P2s about what the check can and cannot see, and one sentence in the post.
Checks. make bundle-test: green, test_mac_updates.py 21 tests, test_bundle_mac.py 2 tests in 54.3s. Docs' Mac bundle built end to end with the kit resolved from this branch (OFICINA_NATIVE_PACKAGE_PATH, Docs chore/colophon-followups 6974236, which has deleted its LICENSE.lucide): exit 0, not refused, and nothing named Lucide in the bundle at any depth. The Hub CI unzip failure is the runner's, as on #10 and main. Docs', Calls' and Memoria's native trees have no "lucide" in any Swift or SVG (Docs has none in any file), so none is refused, as you say.
Scope is right. apple/Sources, ios and macos hold the apps' sources, project.ymls, xcassets and generated .xcodeprojs; Docs' derived data goes to native/build/, and SwiftPM's checkouts to apple/.build/, so the walk never reads a dependency's sources and cannot be tripped by a stale build. The error is raised before swift build, as the test asserts through the absence of build/.
P2 — the scan checks the convention, not the drawing. It keys on the word "lucide". That is the house convention (rastrillo's icons.go: paste the paths, "keep the (Lucide: <slug>) comment"), and every Lucide use in the fleet carries it. But the comment says "any Swift source or SVG in the app that names Lucide means the app draws it, and must carry it", which is true one way only. Probed with a scratch app and no LICENSE.lucide: file-text.svg as the lucide repo ships it (no class, no name anywhere in the file) under ios/Assets.xcassets, plus a Path pasted into a Swift file without its comment, passes the scan and reaches swift build. A PDF or PNG in an xcassets, and an icon package pulled in through Package.swift, are not looked at. Say so in the comment and in APPLE.md: it catches a named use, which is why uses are named. Not blocking: an unnamed copy was invisible before this branch too, and the branch only adds a check.
P2 — a mention is a refusal, and the message then says the wrong thing. Probed: a Swift file holding only // Unlike the web chrome, which draws Lucide, this app uses SF Symbols only. is refused with "apple/Sources/P/Icons.swift draw Lucide, but the app has no LICENSE.lucide". The refusal is loud, so nothing ships wrong, but the way out it offers ("restore it") would make an app carry a notice it does not owe, and Docs' TestTheAppsCarryNoLucide would then fail it. Either match the convention's mark (Lucide:, or lucide-/lucide.dev) rather than the bare word, or word the message as "names Lucide" and offer both exits: restore the file, or reword the mention. Also "draw" with one file.
P2 — "Docs, Calls and Memoria run this script" is two of three. Calls (meet) runs its own copy, native/scripts/bundle-mac.py, whose line 21 copies LICENSE.lucide unconditionally; only Docs and Memoria run the kit's tool ($shared_native/tools/bundle-mac.py). So this refusal reaches Docs and Memoria, and Calls' follow-up is to adopt the kit's tool or make its copy conditional before deleting its file, not just to bump a pin. Post and follow-up list only; nothing in the code.
Nit. path.suffix in (".swift", ".svg") is case-sensitive; an .SVG is skipped.
Comment style. The new comment now names the failure prevented (an app that draws Lucide and lost the file shipping without the notice, silently), which AGENTS.md asks for. The copy comment's last sentence ties the two halves together. Good.