launchctl CoreFoundation spike

Picking the CoreFoundation source for the launchd-842 launchctl port. Apple's launchctl.c is 4,549 LOC of CF-heavy code. Three candidate implementations are on the table: gnustep/libs-base, gnustep/libs-corebase, and swiftlang/swift-corelibs-foundation. This spike audits each repo against the exact symbol set launchctl.c uses and produces a buy/build/skip decision per candidate, with a concrete porting plan for the winner.

Companion to the libxpc Foundation spike (which scoped Foundation at the project level) and the launchd-842 porting plan (which schedules launchctl as task I1e). This spike is narrower: it's specifically about which CF source launchctl.c compiles and links against on FreeBSD.

Contents

  1. 1. The problem — why pick CF now
  2. 2. What launchctl.c actually needs
  3. 3. gnustep/libs-base — audit
  4. 4. gnustep/libs-corebase — audit
  5. 5. swift-corelibs-foundation — audit
  6. 6. Side-by-side matrix
  7. 7. Decision
  8. 8. Porting plan for the winner
  9. 9. Non-CF surface (IOKit, Mach-O, NSSystemDirectories)
  10. 10. Coexistence with GNUstep — one CF per binary
  11. 11. Why not rewrite to GNUstep instead?
  12. 12. What this means for the GNUstep roadmap
  13. 13. Future: what if we want Swift on the ISO?
  14. 14. Impacts of these decisions — full accounting
  15. 15. Open questions / follow-ups
  16. 16. Methodology & audit provenance

1. The problem — why pick CF now

The launchd daemon itself (/sbin/launchd, 235 KiB ELF) was the last big I1c milestone — it builds, links, execs, smoke-passes. Audit of the seven hand-written daemon TUs (launchd.c + core.c + runtime.c + ipc.c + log.c + kill2.c + ktrace.c) finds zero CoreFoundation calls: the daemon reads plists through the lower-level launch_data_t API in liblaunch. So nothing about the daemon depends on this decision.

The downstream consumer that does depend on it is launchctl (the user-facing CLI). support/launchctl.c in the launchd-842 vendor drop is 4,549 LOC, heavily plist-driven, and pulls in <CoreFoundation/CoreFoundation.h>, <CoreFoundation/CFPriv.h>, and <CoreFoundation/CFLogUtilities.h>. The default plan was "port launchctl after the GNUstep stack lands"; this spike re-examines that with actual symbol-level evidence.

Why now and not later: "let's port launchctl with a minimal CF shim and patch the gaps as they surface" is a viable plan only if the gaps are small. A 245-grep on launchctl.c for CF* identifiers fueled an early fear that the gap was large. This spike puts a precise number on it: 49 distinct CF function calls, 1 CF SPI symbol, total surface ~90 identifiers including types and constants. That's small enough that the choice of CF source matters a lot — one candidate with broken plist parsing is fatal, but a different one with 48-of-49 coverage is essentially done.

2. What launchctl.c actually needs

Full audit at launchctl_api_audit.md in the working tree. Grep methodology in §12. Summary:

BucketDistinct symbolsTreatment for FreeBSD port
Public CF API — functions48Must be present in the CF source we pick.
Public CF API — types17Standard CF*Ref typedefs; in any CF header.
Public CF API — kCF* constants25Allocators, booleans, compare, number types, plist mutability, callbacks — in any CF header.
CF SPI (<CFPriv.h> + <CFLogUtilities.h>)1Only _kCFSystemVersionBuildVersionKey. CFLog() is included but never called. The CFLogUtilities.h include can be dropped from launchctl.c.
IOKit7Stub no-ops on FreeBSD. Both call paths (IOKitWaitQuiet in fsck/single-user, IORegistryEntryFromPath in do_bootroot_magic) are macOS-specific feature paths.
Mach-O1getsectiondata only inside #if TARGET_OS_EMBEDDED. Zero impact.
<NSSystemDirectories.h>9Hand-rolled table replacement (4 paths + a domain bitmask). None of the CF candidates ship this header.

The 49 distinct CF function calls (CF functions + macros, deduped) are the gold-standard "does this CF source compile launchctl" checklist:

CFArrayAppendValue            CFArrayCreateCopy             CFArrayCreateMutable
CFArrayGetCount               CFArrayGetTypeID              CFArrayGetValueAtIndex
CFBooleanGetTypeID            CFBooleanGetValue
CFDataCreateWithBytesNoCopy   CFDataGetBytePtr              CFDataGetLength
CFDataGetTypeID
CFDictionaryAddValue          CFDictionaryApplyFunction     CFDictionaryCreateCopy
CFDictionaryCreateMutable     CFDictionaryGetCount          CFDictionaryGetKeysAndValues
CFDictionaryGetTypeID         CFDictionaryGetValue          CFDictionaryRemoveValue
CFDictionarySetValue
CFErrorCopyDescription
CFGetTypeID                   CFRelease                     CFRetain
CFNumberCompare               CFNumberCreate                CFNumberGetType
CFNumberGetTypeID             CFNumberGetValue
CFPropertyListCreateFromStream    CFPropertyListCreateFromXMLData
CFPropertyListCreateWithData      CFPropertyListCreateXMLData
CFRangeMake
CFReadStreamClose             CFReadStreamCopyError         CFReadStreamCreateWithFile
CFReadStreamOpen
CFSTR
CFStringCompareWithOptions    CFStringCreateWithBytes       CFStringCreateWithCString
CFStringCreateWithCStringNoCopy   CFStringGetCString        CFStringGetLength
CFStringGetTypeID
CFURLCreateDataAndPropertiesFromResource    CFURLCreateFromFileSystemRepresentation
CFURLWriteDataAndPropertiesToResource

The 25 kCF* constants are the standard set: kCFAllocatorDefault, kCFAllocatorNull, kCFAllocatorSystemDefault, kCFBooleanFalse, kCFBooleanTrue, kCFCompareEqualTo, the full kCFNumber*Type family (10 of them), kCFPropertyListImmutable, kCFPropertyListMutableContainersAndLeaves, kCFStringEncodingUTF8, kCFTypeArrayCallBacks, kCFTypeDictionaryKeyCallBacks, kCFTypeDictionaryValueCallBacks.

The 17 types are exactly the CF*Ref typedef set you'd expect (CFArrayRef, CFBooleanRef, CFDataRef, CFDictionaryRef, CFErrorRef, CFIndex, CFMutableArrayRef, CFMutableDictionaryRef, CFNumberRef, CFNumberType, CFPropertyListFormat, CFPropertyListRef, CFReadStreamRef, CFStringRef, CFTypeID, CFTypeRef, CFURLRef).

The one SPI symbol — _kCFSystemVersionBuildVersionKey from <CoreFoundation/CFPriv.h> — is referenced once at launchctl.c:4341 inside copySystemBuildVersion() to read /System/Library/CoreServices/SystemVersion.plist. It's literally CFSTR("ProductBuildVersion"). Even a CF source with no SPI surface can have this one constant pasted in.

Headline finding. launchctl.c is essentially a pure-public-CF client. The SPI surface is a single string constant. CFLogUtilities.h is included but unused. The IOKit/Mach-O/NSSystemDirectories surface is small enough to stub locally regardless of which CF candidate we pick.

3. gnustep/libs-base — audit

repo github.com/gnustep/libs-base · LGPL/GPL · ~253k LOC across 221 .m files · cloned at /Users/jmaloney/Documents/launchd/libs-base · full audit at libs-base_audit.md.

libs-base is the GNUstep Foundation implementation — NSString, NSArray, NSDictionary, NSPropertyList, etc. It is the natural pair for libs-corebase and the right answer for any Foundation/NSXxx consumer.

CF C API exposure

Toll-free bridging

No __CFRuntimeBase head in NSObject, no CFTypeID registry, no CFAllocator plumbing. The GNUstep tradition is explicitly that NS and CF are not toll-free on this side. Apple-source code that hands an NSDictionary* across a "looks like a CFDictionaryRef" boundary would crash.

NS Foundation coverage (for reference)

Broad and mature. NSPropertyListSerialization in Source/NSPropertyList.m (4,305 lines) handles XML v1.0, binary v1.0 (bplist00 — both GSBinaryPLParser and GSBinaryPLGenerator), OpenStep ASCII, plus GNUstep ASCII + GNUstep binary extensions. All of which is great for Foundation consumers and irrelevant to launchctl.

Build deps

gnustep-make (tools-make) + libobjc2 + libffi + libxml2 + libicu + libgnutls + libdispatch (+ optional avahi/libxslt). Effectively the existing gershwin/GNUstep stack.

Verdict for launchctl: unsuitable libs-base contributes nothing to a pure-C-CF launchctl. It remains entirely appropriate for the GNUstep app/framework side of the scope split, but it is not the launchctl story.

The side question raised in the audit — "could we build a small Objective-C shim that exposes CFFooBar() signatures wrapping libs-base NSXxx?" — is technically feasible but in practice is many person-weeks of mechanical glue, drags the full libobjc2 + libs-base + ICU + libxml2 + gnutls runtime into every launchctl invocation, and crucially is not toll-free with a real CoreFoundation, so any future handoff to Apple-source CF code would crash. Not pursued.

4. gnustep/libs-corebase — audit

repo github.com/gnustep/libs-corebase · LGPL 2.1 · ~37k LOC (26k C + 2.4k ObjC + 7.9k headers) · HEAD c629a11 2026-04-26 pkgdemon merge; real activity ended 2021-09-30 · cloned at /Users/jmaloney/Documents/launchd/libs-corebase · full audit at libs-corebase_audit.md.

libs-corebase is the GNUstep CoreFoundation implementation — specifically a C-only CF API clone, the right shape for our problem on paper.

Public CF API coverage

909 distinct CF* function definitions. The core data types (CFString/Array/Dict/Number/Date/Data/UUID/Set/RunLoop/Socket/Stream/Tree/BitVector/CharacterSet/Calendar/Locale/{Date,Number}Formatter/Error) are genuinely implemented with ICU backing. All 49 of launchctl's CF function calls listed in §2 have prototypes here.

The PropertyList blocker

This is fatal for launchctl. Source/CFPropertyList.c:

launchctl's whole job is loading .plist job descriptions, which Apple ships as either XML or binary plist. Neither format reads through libs-corebase as it stands today.

CF SPI

No CFPriv.h, no CFLog, no CFLogUtilities.h. The _CF* underscore symbols present are GNUstep's own bridging SPI (_CFRuntimeRegisterClass, _CFBridgingRetain, etc.), not Apple's launchd SPI.

Other gaps worth noting

Build deps

gnustep-make + libobjc + libs-base + ICU + libm. libdispatch optional but recommended for CFRunLoop main-queue integration + CFSocket dispatch sources. Not standalone — pulls libs-base in via the NSCF bridge classes.

Verdict for launchctl: fatally incomplete The plist code paths (XML read, binary read, binary write) are stubs/return NULL/empty bodies. Without working plist parsing, launchctl cannot load a single job. We would be writing the plist driver ourselves to make libs-corebase functional, which is most of the value of picking a real CF source in the first place.

That said: libs-corebase covers 909 CF functions, which is a much wider surface than launchctl needs. The breadth is real value if a project consumer outside launchctl ends up needing the secondary CF types (CFCalendar, CFDateFormatter, CFBag, CFBitVector, etc.). For launchctl specifically, the plist gap dominates.

5. swift-corelibs-foundation — audit

repo github.com/swiftlang/swift-corelibs-foundation · Apache-2.0 · HEAD 0e20e4a 2026-05-13 (actively maintained, master up-to-date) · cloned at /Users/jmaloney/Documents/launchd/swift-corelibs-foundation · full audit at swift-corelibs-foundation_audit.md.

swift-corelibs-foundation is the open-source Foundation port originally for Swift on Linux. The relevant part for us is Sources/CoreFoundation/a real CoreFoundation C implementation derived from Apple's CF-Lite drops, with Swift refcount hooks added on top.

CF C source presence

Sources/CoreFoundation/ ships 78 standalone CF*.c files, ~97,738 LOC total. Highlights for the launchctl-relevant subset:

FileLOCNotes
CFString.c7,946Full mutable/immutable string, encodings, formatting, transforms.
CFURL.c5,494Complete CFURL impl.
CFPropertyList.c3,288XML + OpenStep + binary plist driver. Real, not stubbed.
CFBinaryPList.c1,815Real Apple binary plist reader+writer (bplist00).
CFOldStylePList.c760OpenStep / NeXT plist parser.
CFRuntime.c1,867The CF class/refcount runtime.
CFNumber.c / CFArray.c / CFDictionary.c / CFData.c / CFSet.c1,322 / 1,039 / 437 / 870 / 339Standard containers + Number.
CFUtilities.c1,667CFLog, CFShow, hashing.
CFReadStream/CFStream.c1,977CFReadStream/CFWriteStream + dispatch.
CFBundle.c + 11 CFBundle_*.c1,925 + ~7kFull bundle impl.

These are not stripped-down — file headers say "Copyright (c) 1998-2019, Apple Inc." with Swift runtime hooks added. The plist code in particular is the real Apple driver, exercised by the Swift-side plutil(1) reimplementation included in the same repo.

Public CF API coverage

915 distinct CF* function definitions with real bodies in Sources/CoreFoundation/*.c. Cross-checked against launchctl's 49-function list: 48 of 49 present. The one missing symbol is CFPropertyListCreateFromFile — a deprecated thin wrapper. launchctl can substitute CFReadStreamCreateWithFile + CFPropertyListCreateFromStream (both present); one-line patch.

CF SPI

The full Apple-style "private but exposed" surface ships:

This is essentially the same SPI launchd-842 was written against. Both CFPriv.h and CFLogUtilities.h are header-and-impl complete.

PropertyList support

Both XML and binary, both read and write, all with real implementations:

The Swift-runtime caveat

This is the gotcha. CF source ships as a C-only static library on disk, but the build and the runtime are wired to Swift in three places:

  1. Sources/CoreFoundation/internalInclude/CoreFoundation_Prefix.h:10 hard-defaults DEPLOYMENT_RUNTIME_SWIFT to 1.
  2. Top-level CMakeLists.txt:181-194 further asserts -DDEPLOYMENT_RUNTIME_SWIFT -DCF_BUILDING_CF -fcf-runtime-abi=swift -fblocks -fconstant-cfstrings.
  3. Sources/CoreFoundation/CMakeLists.txt:118-121 links _FoundationICU (fetched via CMake from swift-foundation-icu) and dispatch.

With DEPLOYMENT_RUNTIME_SWIFT=1 the CF C runtime takes the Swift-RC path: _CFRetain() becomes swift_retain((void *)cf), _CFRelease() becomes swift_release(void *), init calls __CFInitializeSwift(), and CF class table slots get their ObjC class from __CFSwiftGetBaseClass(). None of those symbols are defined in Sources/CoreFoundation/ — they come from Sources/Foundation/ (Swift) and from libswiftCore.so.

The legacy non-Swift refcount path is fully present in the source — the long bitfield CAS loop in CFRuntime.c:1430-1509 — but is #if'd out by the SwiftRT macro. To build standalone C-only we need to:

  1. define DEPLOYMENT_RUNTIME_SWIFT=0 before the prefix header is processed (patch CoreFoundation_Prefix.h:11),
  2. remove -fcf-runtime-abi=swift from compile flags,
  3. write our own build wrapper (small CMake or Makefile) that drops _FoundationICU and (optionally) dispatch from target_link_libraries, and either provides them from system or drops the .c files that need them. ICU consumers: CFLocale, CFCalendar, CFDateFormatter, CFNumberFormatter, CFStringTransform, CFRegularExpression, CFCharacterSet, CFStringEncodingDatabase, CFICUConverters. None of those are referenced by launchctl, so the relevant .c files can be dropped entirely.

The upstream build does not expose a "C-only" target. We have to maintain our own build wrapper. Estimated effort: about a day of CMake/Makefile work plus a small patch series on the prefix header. The legacy non-Swift code path itself is intact upstream and tested — we are flipping a knob, not reviving abandoned code.

Dependencies (post-strip)

Verdict for launchctl: recommended swift-corelibs-foundation CF covers 48 of 49 launchctl CF calls, has working XML + binary plist read/write, ships CFPriv.h and CFLogUtilities.h with real impls, and stays under Apache 2.0. Cost: about one engineer-day of build-system surgery (fork prefix header, write a CMake/Makefile target that emits libCoreFoundation.so, drop ICU-only sources). After that, launchctl ports against a real Apple-shape CF.

6. Side-by-side matrix

Capabilitylibs-baselibs-corebaseswift-corelibs-foundation
CF C function definitions0909915
Coverage of launchctl's 49 CF function calls0 of 4949 of 49 (decls)48 of 49 (real impl)
XML plist readvia NSreturn NULLreal (3,288 LOC)
XML plist writevia NSpartialreal
Binary plist readvia NS#if 0real (1,815 LOC)
Binary plist writevia NSempty {}real
CFPriv.hnono138 decls
CFLogUtilities.h + CFLog implnonoyes
Standalone-buildable todayyes (NS only)yesneeds prefix-header fork + build wrapper (~1 day)
LicenseLGPL/GPLLGPL 2.1Apache 2.0
Upstream activityactive2021-09-30 (one merge 2026-04)2026-05-13, active
Build deps (minimum)gnustep-make + libobjc2 + libffi + libxml2 + ICU + libgnutls + libdispatchgnustep-make + libobjc + libs-base + ICU + libmlibdispatch + libBlocksRuntime (ICU optional — drop for launchctl)
Fit for launchctlno (no CF surface)no (plist stubs)yes

7. Decision

Pick: swift-corelibs-foundation CoreFoundation/ as the CF source for launchctl, built standalone (non-Swift refcount path) with the ICU-only .c files dropped.

The matrix collapses to one viable answer. libs-base contributes nothing on the CF axis. libs-corebase has the right shape but the plist code paths are stubs — and plist parsing is launchctl's primary job. swift-corelibs-foundation's CF source carries the real Apple plist driver, ships the SPI surface launchctl-842 was written against, and stays under Apache 2.0.

The Swift-runtime entanglement is real but bounded: it's a knob (DEPLOYMENT_RUNTIME_SWIFT) the codebase still respects. We flip it, drop ICU-only sources, and write our own build target.

8. Porting plan for the winner

Concrete steps, ordered. Each step is a CI-checkpointable milestone.

8.1 Vendor

Copy Sources/CoreFoundation/ + Sources/CoreFoundation/include/ + Sources/CoreFoundation/internalInclude/ + uuid/ from ../swift-corelibs-foundation into src/libCoreFoundation/ in the freebsd-launchd-mach repo. Match the libdispatch precedent: vendored source, repo-local patches, no submodule.

8.2 Keep ICU files; install ICU as a build dep

Originally proposed: strip the nine ICU-consuming .c files (CFLocale*, CFCalendar, CFDateFormatter, CFNumberFormatter, CFStringTransform, CFRegularExpression, CFCharacterSet, CFStringEncodingDatabase, CFICUConverters). Revised after discussion: keep them, install ICU instead. Reasons:

Build dep added: libicuuc + libicui18n + libicudata + their -dev headers (via FreeBSD devel/icu port or pkgbase if available). Add to buildpkgs.txt / pkglist.txt. Runtime cost on the ISO: ~30 MiB across the three libs.

The only subdir we still drop is src/libCoreFoundation/BlockRuntime/ — that's the WASI fallback for systems without a real libBlocksRuntime. We already ship /usr/lib/system/libBlocksRuntime.so via the libdispatch build (per the post-2026-05-13 bundling decision), so the upstream fallback is redundant.

8.3 Disable the Swift RC path

Patch internalInclude/CoreFoundation_Prefix.h:11 to set DEPLOYMENT_RUNTIME_SWIFT 0, or #undef + #define it explicitly. Confirm by inspection that CFRuntime.c's legacy bitfield-CAS path is now active and swift_retain/swift_release/__CFInitializeSwift/__CFSwiftGetBaseClass references are #else'd out.

8.4 Write a bsd.lib.mk Makefile

Single src/libCoreFoundation/Makefile following the precedent set by liblaunch, libsystem_kernel, libxpc:

8.5 Add build.sh step + smoke marker

Following the precedent of step 3i (libxpc) and 3m (liblaunch): a new build.sh step (call it 3p or wherever) that make -C src/libCoreFoundation's the lib, ldconfig-hints it (already covered by /etc/ld-elf.so.conf/usr/lib/system is on it), and builds a tiny test_corefoundation.c at /usr/tests/freebsd-launchd-mach/test_corefoundation that round-trips a small dict through CFPropertyListCreateData + CFPropertyListCreateWithData in both XML and binary modes. Marker: COREFOUNDATION-OK. Expect grammar entry in tests/boot-test.sh.

8.6 Patch launchctl.c for the one missing symbol

Replace the single CFPropertyListCreateFromFile call (line ~526 vicinity per earlier audit) with a two-liner: CFReadStreamCreateWithFile + CFPropertyListCreateFromStream. Delete the unused #include <CoreFoundation/CFLogUtilities.h>.

8.7 Build launchctl

Mirrors the launchd daemon's Phase I1c flow: a src/launchd/support/Makefile using bsd.prog.mk, PROG=launchctl, SRCS=launchctl.c, BINDIR=/bin (per the install-layout spike — not /sbin; launchctl is on /bin). Link -lCoreFoundation -llaunch -lxpc -lsystem_kernel -lutil -lpthread. The non-CF shims (IOKit no-ops, NSSystemDirectories hand-roll, os/assumes.h reuse from the daemon build) live alongside.

8.8 LAUNCHCTL-BUILD-OK smoke marker

Boot-time /bin/launchctl help — the no-IPC CLI path, parallel to the LAUNCHD-BUILD-OK approach for the daemon. Expected output: the usage banner. Marker: LAUNCHCTL-BUILD-OK.

Estimated total effort

One to two engineer-days for steps 8.1–8.5 (the CF library), plus an unknown but bounded amount for 8.6–8.8 (the launchctl shims for the IOKit/NSSystemDirectories surface). The latter is at most a small .c file of stubs — smaller than what we already wrote for the launchd daemon's libsystem_kernel stubs.

9. Non-CF surface (IOKit, Mach-O, NSSystemDirectories)

Outside the CF question, launchctl.c pulls in three small macOS-only surfaces. None of the three CF candidates ships these — they're handled locally in the launchctl port:

9.1 IOKit (7 symbols, 2 call paths)

SymbolCall pathTreatment
IOKitWaitQuietdo_potential_fsck, single-user, crash-debug (lines 2153, 2219, 2481)No-op stub. FreeBSD has no IOKit registry to quiesce.
IORegistryEntryFromPath
IORegistryEntryCreateCFProperty
IOObjectRelease
io_service_t
kIOMasterPortDefault
kBootRootActiveKey
do_bootroot_magic (lines 4501-4513)Stub do_bootroot_magic to a no-op or surround with #ifdef __APPLE__. There's no IODeviceTree:/chosen on FreeBSD.

9.2 Mach-O (1 symbol)

getsectiondata, single call site at line 1717 inside GetPropertyListFromCache(), which is wrapped in #if TARGET_OS_EMBEDDED. We don't define TARGET_OS_EMBEDDED on FreeBSD; the whole code path drops out. The #include <mach-o/getsect.h> itself needs the same guard. Zero ELF-equivalent work needed.

9.3 <NSSystemDirectories.h> (9 symbols)

Sole consumer is load_and_unload_cmd() (lines 2631-2769) iterating /.../Library/LaunchAgents + LaunchDaemons per domain. Replace with a hand-rolled domain table:

static const struct {
    unsigned int mask;
    const char *path;
} _ns_dirs[] = {
    { NSUserDomainMask,    "~/Library" },
    { NSLocalDomainMask,   "/Local/Library" },   /* project policy: /Local replaces /Library */
    { NSNetworkDomainMask, "/Network/Library" },
    { NSSystemDomainMask,  "/System/Library" },
};

Plus a one-function reimplementation of NSStartSearchPathEnumeration / NSGetNextSearchPathEnumeration against that table. ~30 lines of C.

9.4 Other Apple-only headers (already handled by the daemon shims)

<os/assumes.h>, <libinfo.h>, <bootfiles.h>, the BSM auditd header — all of these already have shims in src/launchd/freebsd-shims/ from the I1c launchd-daemon build. They get reused as-is.

10. Coexistence with GNUstep — one CF per binary

Natural follow-up question, since gershwin already ships libgnustep-base and the GNUstep stack provides its own libgnustep-corebase with the same CF* API surface as the swift-corelibs CF we're recommending: do these two CoreFoundation implementations conflict if both are installed on the same system?

Answer: they coexist by design, provided no single binary links against both.

10.1 Why they don't conflict at the system level

Each CF runtime is a separately-named ELF shared library with its own SONAME and its own type registry:

Propertyswift-corelibs CFgnustep-corebase
Install path/usr/lib/system/libCoreFoundation.so.6/System/Library/Libraries/libgnustep-corebase.so.0 (gershwin convention)
SONAMElibCoreFoundation.so.6libgnustep-corebase.so.0
ConsumersApple-source: launchctl, future configd, asl, notifyd, …GNUstep apps + frameworks (toll-free with libgnustep-base)
Header root/usr/include/CoreFoundation/gershwin's GNUstep tree (/System/Library/Headers/CoreFoundation/ or similar — gershwin's call)
Type registryinternal to libCoreFoundation.so.6internal to libgnustep-corebase.so.0

Each binary's DT_NEEDED records exactly one of the two library names. ldconfig finding both shared objects in different directories is harmless — the dynamic linker resolves DT_NEEDED libCoreFoundation.so.6 by name and goes to /usr/lib/system/; it doesn't see libgnustep-corebase.so.0 at all. The two libraries can sit on the same disk indefinitely without anything bridging them.

10.2 Where the conflict WOULD happen (the rule)

CoreFoundation is a typed runtime, not just an API surface. CFArrayGetTypeID() returns a number that comes from the loaded CF's type-registry init — assigned at first call, different between the two implementations. A CFArrayRef created by one CF is just a pointer to an internal struct; the other CF doesn't recognize it as an array (its type ID is different), and CFRelease against the wrong runtime would either crash or corrupt refcounts. Same goes for the per-type vtables: each CF binds CFArrayCreate's function pointer at init time to its own copy of the implementation, and the two copies do not share any state.

So the project-wide rule:

One CF per binary. No single executable or shared library links both libCoreFoundation (swift-corelibs) and libgnustep-corebase. Pick one per consumer based on which lane the consumer is in:

If a future need to mix surfaces shows up — e.g. a GNUstep app that wants to call into a CF-typed Apple library — the answer is not "link both CFs in the same process". The answer is one of: (a) the libgnustep-corebase side already provides the API, route through it; (b) the Apple-shape library exposes a non-CF entry point (the way launchd exposes launch_data_t, not CF); or (c) an explicit RPC boundary that does not pass CF objects across the link.

10.3 Header paths matter too

If both libs ever installed headers to the same /usr/include/CoreFoundation/, second-install wins and every translation unit compiled after that point picks up the wrong types. Different header roots prevent it:

Both .pc files (if pkg-config is used) get distinct names — libCoreFoundation.pc vs libgnustep-corebase.pc. Consumers ask for one or the other explicitly.

10.4 What this means in practice for the launchctl port

The port is unaffected by the question of whether gershwin's CF stack is installed. launchctl links -lCoreFoundation from /usr/lib/system/ and compiles against headers from /usr/include/CoreFoundation/. It has no knowledge of gershwin's GNUstep stack and doesn't need to. Installing gershwin on the same system puts libgnustep-corebase.so.0 on disk in a different directory; the two CoreFoundations coexist without colliding at link time or at runtime.

This is the same posture gershwin already takes with libdispatch: gershwin ships libdispatch through its own pkg, and we ship our own libdispatch via /usr/lib/system/libdispatch.so. The two installations don't fight because each binary's link line picks one and the other is invisible to it.

11. Why not rewrite to GNUstep instead?

The obvious alternative: skip CoreFoundation entirely, rewrite each Apple system service we want to port (launchctl, configd, IPConfiguration, mDNSResponder, asl, …) to use GNUstep NSDictionary/NSArray/NSString in place of the CF equivalents. The project would then have one Foundation stack (GNUstep) for the entire userland.

This sounds clean. In practice it's worse than the swift-corelibs CF path by every meaningful axis. Four reasons:

11.1 CF is structural in these daemons, not decorative

For launchctl alone, CF is "the plist parser plus a few container helpers" — ~50 distinct functions per the audit. For the bigger Apple-source daemons further out on the roadmap, CF is the data model and the event model:

"Strip CF and rewrite to NS" for IPConfiguration is a multi-month rewrite. For configd it's larger still.

11.2 configd's public API is CF-shaped

Even setting the daemon internals aside, configd exposes its surface as SystemConfiguration.framework — a CF-typed cross-process store API. Every Apple-source binary that asks the system "what's my hostname?" / "am I on a captive portal?" / "what are my network interfaces?" (and that's a lot of them: mDNSResponder, network setup helpers, security extensions, GUI network prefs equivalents) talks SystemConfiguration in CF terms.

Rewriting configd to use NS internally means rewriting the public API too, at which point every consumer of SystemConfiguration stops compiling against our fork. We'd be maintaining a divergent fork of every consumer for as long as the project exists. That's the spiral the libxpc plan explicitly warned against early.

11.3 The cost is asymmetric and one-way

PathOne-time costPer-daemon costCost on each upstream refresh
swift-corelibs CF~1 day to vendor + strip ICU + flip Swift macro + write Makefileonly the daemon-specific platform shims (mach, kqueue, BSD socket dialect, …)re-vendor; daemon source remains upstream-compatible
Rewrite to GNUstep NS(nothing — GNUstep already exists)weeks-to-months per daemon (CFDictionary/CFArray/CFString/CFRunLoop/CFSocket replacement throughout)re-apply the entire NS rewrite to every upstream refresh; binary divergence grows over time

The first row is engineering done once. The second row is engineering done forever, against a moving upstream target, with no path back to "just compile the official source" if the rewrite gets behind.

11.4 The scope_split memory anticipated exactly this case

The project's internal scope-split memo, written before this audit, says:

swift-corelibs CF, deployed for system services only and never linked by apps (per §10's one-CF-per-binary rule), sits cleanly inside the Apple-source-system-services lane. It's the engine the daemons need; it's not a framework apps would link against. The split holds.

Bottom line. The CF-engine path is the one that uses the upstream's work. The NS-rewrite path is the one that replaces it. For a project sized to "get launchd-adjacent plumbing working on FreeBSD," using the upstream is the only viable plan. GNUstep stays exactly where it was — the right answer for the framework/app layer, not blocking the system-services layer.

12. What this means for the GNUstep roadmap

The original plan was: port libobjc2 → tools-make → libs-base → libs-corebase, then launchctl. This spike says none of those are launchctl-blocking — and once we follow the reasoning in §11, none of them are configd-blocking, IPConfiguration-blocking, mDNSResponder-blocking, asl-blocking, or notifyd-blocking either. The Apple system-services layer is pure C with CF, by Apple's original design.

12.1 What the system services actually need from us

Daemon / toolLanguageFoundation needWhat it needs from us
launchctlCCFlibCoreFoundation
configd + SystemConfiguration.frameworkCCF + SystemConfiguration (also CF-shaped)libCoreFoundation + a SystemConfiguration port
IPConfigurationCCF + SystemConfigurationlibCoreFoundation + SystemConfiguration
mDNSResponderCCF on Darwin only (Linux build is CF-free)libCoreFoundation or the existing portable POSIX build path
asl family (syslogd, syslog, libasl, aslmanager)CCF only for some logging API surfacelibCoreFoundation
notifyd + libnotify + notifyutilCnone (Mach-port-driven)nothing CF/NS at all — pure libxpc/libdispatch
DiskArbitration + userland IOKit helpersCCFlibCoreFoundation

12.2 The reshaped Apple-source porting tree

The launchd-adjacent porting roadmap, post-swift-corelibs-CF decision:

libsystem_kernel + libdispatch + libxpc + liblaunch     [done — Phase A–I1b]
                              ↓
                       launchd daemon                   [done — Phase I1c]
                              ↓
              swift-corelibs CoreFoundation             [in flight, task #16–20]
                              ↓
                          launchctl                     [task #21–23]
                              ↓
              SystemConfiguration.framework             [later — configd's API surface]
                              ↓
                          configd                       [later]
                  ↓             ↓             ↓
            IPConfiguration  mDNSResponder   asl + notifyd  DiskArbitration

That whole tree is C-with-CF. GNUstep does not appear in it.

12.3 Where GNUstep is still on the roadmap (just not this roadmap)

  1. The gershwin desktop / app side. Anything NS*-using — the AppKit-equivalent apps, GUI plumbing, app frameworks. Parallel track to the system services, not blocked by them and not blocking them.
  2. CFPlugIn-loaded plugins for configd-class daemons. On Darwin, configd loads NetworkExtension / EAPOLController / EAP plugins that are Obj-C. Those plugins (not the daemon they plug into) would link GNUstep. Without them, the core daemons still run — we just lose plugin features. Defer until/unless we need them.
  3. Future Apple-source binaries with NS surface. Spotlight, AppKit-using daemons, NSTask consumers. None of these are on the launchd-adjacent shortlist. If one shows up, GNUstep is the answer for the NS half — with the one-CF-per-binary discipline from §10.

12.4 Practical consequence: parallel tracks, not a sequential bottleneck

Before this audit the plan was "GNUstep block then Apple-services block," conceptually a single critical-path queue. After the audit, the picture is two independent tracks:

Apple-source system services:  libCoreFoundation → launchctl → configd → IPConf/mDNS/asl/…
                                                            (no GNUstep dep anywhere)

GNUstep / gershwin desktop:    libobjc2 → tools-make → libs-base → libs-corebase → apps
                                                            (no Apple-source-service dep)

If gershwin-developer already provides libobjc2 + libs-base (worth a single pkg search against the gershwin tree to confirm — the gershwin_provides memory confirms libdispatch is shipped; libobjc2/libs-base coverage TBD), the GNUstep track is largely "consume what's there." Either way, the two tracks run in parallel and don't block each other.

That's the headline take-away from this spike: you can ship a working FreeBSD with launchd + configd + IPConfiguration + mDNSResponder + asl + notifyd + DiskArbitration without ever building libobjc2 or libs-base, and then layer gershwin on top of that whenever the desktop work picks up.

13. Future: what if we want Swift on the ISO?

The choice to flip DEPLOYMENT_RUNTIME_SWIFT=0 in our libCoreFoundation raises a natural question: does that close the door on shipping Swift programs (or the Swift toolchain itself) on FreeBSD later? Short answer: no. Swift gets its own lane, just like GNUstep got its own lane, and the three coexist by the same one-CF-per-binary discipline established in §10.

13.1 What "Swift on the ISO" actually means in pieces

"Swift" splits into three things that are technically independent:

  1. Swift toolchain — the swift / swiftc compiler + REPL + Foundation/XCTest for building Swift code on the system. FreeBSD has a swift-lang pkg.
  2. Swift runtimelibswiftCore.so + friends, needed by any program written in Swift. Comes from the same toolchain.
  3. Swift Foundation — swift-corelibs-foundation's Swift NS* classes layered over a Swift-RC-mode CF. Distributed as libFoundation.so (Swift) which on Linux/BSD statically embeds the Swift-mode CF C library inside itself. There's no upstream-shipped standalone libCoreFoundation.so in Swift mode — it's an internal static archive of the Swift Foundation build.

For Swift programs to work on the ISO we need (1) and (2) installable as a pkg. Item (3) ships with (1) when a Swift program imports Foundation.

13.2 Three lanes, three CoreFoundations, no collisions

Once Swift lands on the system, there are three CF implementations on disk:

LaneCF artifact on diskSONAMERC modeConsumers
Apple-source system services/usr/lib/system/libCoreFoundation.so.6 (ours)libCoreFoundation.so.6legacy bitfield-CASlaunchctl, configd, IPConfiguration, mDNSResponder, asl, notifyd, DiskArbitration
GNUstep / gershwin desktop/System/Library/Libraries/libgnustep-corebase.so.0 (gershwin)libgnustep-corebase.so.0GNUstep refcount (toll-free with libgnustep-base NSObject)GNUstep apps + frameworks
Swift runtimestatically embedded inside libFoundation.so (Swift); no standalone shared libn/a (private to libFoundation)Swift ARC (swift_retain/swift_release)Swift programs that import Foundation

The Swift Foundation's CF is statically linked into libFoundation.so — it doesn't export a libCoreFoundation.so file at all, so there's nothing for our SONAME to collide with. Apple's own macOS does the same trick: Swift's Foundation bundles a Swift-RC CF internally, separate from the system CoreFoundation.framework.

13.3 Why the lanes don't fight

The mechanism is identical to the GNUstep coexistence story in §10, just with one more lane:

13.4 Installing the Swift toolchain alongside our libCoreFoundation

Concrete picture for a future Swift-on-ISO sub-project:

13.5 The one combination that wouldn't work

A single binary that wants to be both an Apple-source-system-service (linking our libCoreFoundation) and a Swift program (linking Swift's libFoundation) is the one configuration that would break. Two CFs in one address space, two refcount conventions, two type registries — objects from one would crash when handed to the other.

This is a contrived combination — we don't ship anything that mixes those worlds — but the rule is the same as §10: one CF per binary, generalized now to three lanes. If a future use case forces "Swift code that needs to talk to a system service", the right answer is an RPC boundary (xpc / launchd / mach IPC), not double-linking.

13.6 Could we later switch our libCoreFoundation to Swift mode?

Technically yes — the DEPLOYMENT_RUNTIME_SWIFT macro is a build-time choice, and the legacy code path being #else'd not deleted means we could flip it back. But:

So — flipping back is technically possible, practically pointless. The non-Swift choice is forward-compatible because Swift programs come with their own bundled CF anyway; we never need our build to be Swift-aware.

Bottom line on Swift coexistence. Choosing DEPLOYMENT_RUNTIME_SWIFT=0 does not close the door on Swift on the ISO. Swift lives in its own lane with its own bundled CF (statically linked inside libFoundation.so); our libCoreFoundation lives in the system-services lane; GNUstep lives in the desktop/app lane. Three lanes, no shared SONAMEs to collide, same one-CF-per-binary discipline. Adding Swift later is straightforward and changes nothing about the libCoreFoundation we're building today.

14. Impacts of these decisions — full accounting

This section lays out, decision by decision, what we're committing to (and what we're not) so the tradeoffs are visible before any of it lands as code. Six decisions feed this spike's plan, plus a closing audit of cross-cutting risks and what stays reversible.

14.1 Decision: swift-corelibs-foundation as the CF source

Alternatives rejected: libgnustep-corebase (plist parser stubs — fatal); libgnustep-base (zero CF C surface); rewrite every Apple system service to NS (multi-month, per-daemon, permanent fork — see §11).

DimensionImpact
LicenseApache 2.0. We ship the LICENSE file alongside the vendored source. No commercial-use restrictions; the only obligation is preserving copyright notices.
Disk footprint on ISO/usr/lib/system/libCoreFoundation.so.6 ≈ 2-4 MiB stripped (estimate — will measure during §8). Headers at /usr/include/CoreFoundation/ ≈ 1 MiB. Total install ≈ 3-5 MiB.
Source tree on disksrc/libCoreFoundation/ ≈ 10 MiB (78 .c + headers + Unicode tables). One-time cost in the git history.
Maintenance burdenWe carry a small patch series against upstream (Swift macro flip, BlockRuntime/ drop, Makefile addition). Re-vendoring a future upstream release is a recursive copy + patch re-apply.
Upstream drift riskswift-corelibs CF is actively maintained (HEAD 2026-05-13). Upstream's primary code path is Swift mode; legacy non-Swift mode is #else'd but stays compileable. If upstream ever deletes the legacy branch, our patch-set grows.
Forward compatibilityAdding more CF coverage = drop fewer files. Removing CF coverage later = drop more files. Both are build-time toggles, not source rewrites.

14.2 Decision: DEPLOYMENT_RUNTIME_SWIFT=0 (no Swift refcount)

DimensionImpact
Per-process startup costMajor win. Every system service that links libCoreFoundation avoids loading libswiftCore.so (>20 MiB) and the Swift runtime init. launchctl startup stays a small-binary exec + libxpc/libdispatch/liblaunch — no Swift in the address space.
RAM footprint per processSaves ~30-40 MiB of Swift-runtime resident pages per CF-using daemon. Critical for the ISO's "boots in 256 MB" posture.
Build toolchainNo Swift compiler required. Build wrapper is plain clang + bsd.lib.mk.
ABI direction lock-inOne-way SONAME break to flip later. Swift-mode CFTypeRefs are not binary-compatible with non-Swift-mode ones (different refcount layout, different type-table init). If we ever rebuild in Swift mode, we bump to libCoreFoundation.so.7 and re-link every consumer. That's churn we can absorb if the need ever arises.
Risk: legacy refcount bugsThe bitfield-CAS refcount path is real code that ran on macOS for years pre-Swift, but upstream's CI today emphasizes the Swift path. We may hit edge cases the upstream doesn't notice. Mitigation: our smoke tests + the relatively narrow CF surface launchctl exercises catch most.
ReversibilityOne-line revert of the prefix-header patch. Rebuild. Everything still compiles; we just gain a libswiftCore.so dep. So "flip back" is a build-system choice, not a code rewrite.
Effect on future Swift on ISONone — Swift programs live in their own lane with their own bundled CF (see §13). Three lanes, three CFs, zero collision.

14.3 Decision: install at /usr/lib/system/libCoreFoundation.so.6

DimensionImpact
ConsistencyMatches the install-layout spike's Apple-libsystem-family path. Sits alongside libsystem_kernel.so, libxpc.so, libdispatch.so, libBlocksRuntime.so, liblaunch.so.1. Anything reaching for "the Apple system stack" finds them together.
Header path/usr/include/CoreFoundation/. Standard Apple-shape <CoreFoundation/CFFoo.h> resolves naturally.
ldconfig discoveryAlready covered by /etc/ld-elf.so.conf (added in the 2026-05-15 ldconfig migration — /usr/lib/system is on the list). No additional rc.conf surgery.
No /usr/local violationPer project rule. Confirmed.
SOVERSIONSet SHLIB_MAJOR=6 to match Apple's macOS CoreFoundation.framework versioning. Open question (see §15) on whether to match exactly or assign our own.

14.4 Decision: install ICU rather than strip CF's ICU-using files

DimensionImpact
Disk footprintAdds ~30 MiB of ICU libs (libicuuc.so + libicui18n.so + libicudata.so). Largest single chunk is libicudata.so — the Unicode tables.
Build depsAdds devel/icu (or pkgbase equivalent) and its -dev headers to buildpkgs.txt.
CF feature coverageFull upstream coverage: CFLocale, CFCalendar, CFDateFormatter, CFNumberFormatter, CFStringTransform, CFRegularExpression, the Unicode-aware CFCharacterSet, encoding converters. Future configd/IPConfiguration ports don't need to wait for us to un-strip.
gershwin alignmentlibgnustep-base's build already requires ICU. Once gershwin lands, ICU is on the system unconditionally. Installing it for libCoreFoundation costs zero marginal disk in that world.
MaintenanceLess local divergence from upstream — we don't maintain a strip-list that needs re-evaluating each release. Smaller patch series.
Risk: ICU ABI breaksICU SOVERSION bumps about once every two years and is mildly disruptive. We re-link against the new ICU when FreeBSD's pkg updates. Same risk every ICU consumer carries.

14.5 Decision: drop src/libCoreFoundation/BlockRuntime/ subdir, use libdispatch's libBlocksRuntime

DimensionImpact
Why the upstream ships itThe BlockRuntime/ subdir is upstream's fallback for systems without a system libBlocksRuntime.so — primarily WASI. Not for FreeBSD/Linux/Darwin, all of which have a real one.
What we use instead/usr/lib/system/libBlocksRuntime.so built by our vendored swift-corelibs-libdispatch (per the 2026-05-13 "drop FreeBSD-libblocksruntime pkg, bundle via dispatch" decision). Same upstream Apple compiler-rt source as the CF subdir would have used — one canonical copy on the system.
RiskIf we ever swap the libdispatch source for a different upstream that doesn't bundle libBlocksRuntime, we lose our system-wide source. Mitigation: dispatch's libBlocksRuntime is small and stable; the source is in two places (our libdispatch vendor + this CF vendor we just dropped) and we could resurrect the CF subdir if needed.

14.6 Decision: vendor at src/libCoreFoundation/, not as a submodule / external pkg

DimensionImpact
Consistency with prior precedentMatches what we did with libdispatch, libxpc, launchd-842. Single repo, vendored sources, patches in-tree, no submodules.
Build hermeticityCI doesn't need to fetch external repos. Build is reproducible from a single git clone.
Re-vendor costPeriodic cp -R from a fresh upstream clone + patch re-application. Manual but small; the patch surface is intentionally tiny (one prefix-header line + one Makefile).
Provenance trackingsrc/libCoreFoundation/VENDOR file pins upstream SHA + date + license. Same shape as the existing libdispatch precedent.

14.7 What we're NOT committing to

14.8 Cross-cutting risks and mitigations

RiskLikelihoodMitigation
Legacy non-Swift refcount path has latent bugs (Apple's CI deprioritizes it)Low — the path is tested by other downstreams using DEPLOYMENT_RUNTIME_OBJC on LinuxOur COREFOUNDATION-OK smoke marker exercises a real plist round-trip. Bug surfaces show up as test failures or daemon crashes early.
Upstream deletes the legacy refcount branch entirelyLow (would break their own Objective-C/Linux build target)If it ever happens, fork the prior good version. Our patch series is small enough to maintain indefinitely.
libdispatch or libBlocksRuntime ABI breaks under our libCoreFoundationLow — we build both from the same vendored source against the same toolchainCI catches it the same day; we own all the moving pieces.
ICU SOVERSION bump invalidates our libCoreFoundationMedium (every ~2 years)Rebuild against new ICU. Standard FreeBSD-ports churn.
plist parser hits Apple-format edge cases we don't noticeMedium for binary plists with rare types (UID, custom date encoding)We inherit Apple's own driver via swift-corelibs; same code Apple's plutil uses. Bug surface is upstream's bug surface.
SystemVersion.plist SPI symbol (_kCFSystemVersionBuildVersionKey)Low — one constant, well-definedOur libCoreFoundation's CFPriv.h already declares it (per the audit, 138 SPI decls including this one). No special handling needed.
A single binary accidentally links both libCoreFoundation and libgnustep-corebaseLow — different SONAMEs, different install pathsThe one-CF-per-binary rule in §10. CI ldd check could enforce it programmatically if we want extra paranoia.

14.9 What's reversible vs. what's locked in

DecisionReversibility
swift-corelibs-foundation as the CF sourceexpensive Switching CF source forces re-linking every consumer (CFType registries differ). Doable but means revisiting launchctl + every downstream port.
DEPLOYMENT_RUNTIME_SWIFT=0SOVERSION bump Rebuild + libCoreFoundation.so.7 + re-link consumers. Source patches stay tiny.
/usr/lib/system install pathtrivial Move file + ldconfig refresh + re-link. Path is encoded in Makefile.
Install ICU vs striptrivial Build-time toggle. Strip later = drop files from SRCS. Install later = put them back.
Drop BlockRuntime/ subdirtrivial Restore the subdir from upstream, add to SRCS if needed.
Vendor in tree vs submoduletrivial History-rewrite or git-subtree split.
SOVERSION=6 (matching Apple)consumer-visible Changing SOVERSION = every consumer re-link.

Big-picture honesty. The biggest decision in this spike — "use swift-corelibs CF as the CF source" — is expensive to undo, but is well-grounded by the audit evidence (only candidate with working plist support, only one with the SPI surface). The remaining decisions are tactical build-system choices that can flex without major code disruption. The plan optimizes for "small, surgical patches against upstream + ICU/Swift/GNUstep as independent lanes" so the project has room to maneuver as later requirements show up.

15. Open questions / follow-ups

  1. Does gershwin-developer already provide libobjc2 / libs-base? If yes, the GNUstep work for later NS consumers reduces to "add to pkglist". One pkg search resolves it. (The gershwin_provides memory confirms it ships libdispatch; libobjc2/libs-base coverage is unconfirmed.)
  2. SOVERSION for our libCoreFoundation.so: match Apple's libsystem_corefoundation? Or assign our own? Decide before consumers start linking against it.
  3. Install path for the SPI headers: /usr/include/CoreFoundation/CFPriv.h — do we expose the SPI surface to all consumers or hide it? The launchctl source needs it; nothing else does today.
  4. libCoreFoundation runtime deps: confirm at link time that the post-strip library actually only needs libdispatch + libBlocksRuntime + libpthread + libc. If any other transitive symbol surfaces, decide drop-the-file vs add-the-dep.
  5. What if a future Apple-source binary wants more of CF than launchctl does (e.g. needs the CFLocale or CFCalendar surface)? Plan today is "drop the source files we don't use"; if we later need them back, undo the drop — the upstream is preserved verbatim.
  6. Long-term: do we want both swift-corelibs CF and libs-corebase coexisting? The matrix shows they have non-overlapping strengths (libs-corebase covers CFBag/CFBitVector/CFTree/CFAttributedString that swift-corelibs doesn't carry as standalone). For launchctl, no. For project breadth, maybe. Side-by-side SOVERSION question similar to (2).

16. Methodology & audit provenance

This spike is grounded in three pieces of audit work done 2026-05-15 against the cloned repos at /Users/jmaloney/Documents/launchd/{libs-base,libs-corebase,swift-corelibs-foundation}. Methodology was uniform across all three: identify every CF* function definition (function body, not header decl — the gold standard), enumerate CF SPI surface, audit PropertyList implementation specifically, identify build-system entanglements.

Extraction patterns for the launchctl-side inventory (run against launchd-842/src/support/launchctl.c):

grep -oE 'CF[A-Z][A-Za-z0-9_]*' launchctl.c | sort -u    # CF identifiers
grep -oE 'k(CF|cf)[A-Za-z0-9_]*' launchctl.c | sort -u   # kCF constants
grep -oE '_CF[A-Za-z0-9_]*' launchctl.c | sort -u        # _CF SPI
grep -oE '\b_k[A-Za-z0-9_]*' launchctl.c | sort -u       # _k SPI constants
grep -oE 'IO[A-Z][A-Za-z0-9_]*' launchctl.c | sort -u    # IOKit
grep -oE 'k(IO|io)[A-Za-z0-9_]*' launchctl.c | sort -u   # kIO constants
grep -oE '\b(getsect[A-Za-z0-9_]*|getseg[A-Za-z0-9_]*)' launchctl.c | sort -u
grep -oE '\bNS[A-Za-z0-9_]*' launchctl.c | sort -u       # NS*

Junk filter: the IO[A-Z] and NS[A-Z] sweeps caught false-positive prefixes (IONARY, IORITY_REVISION from JETSAM_PRIORITY_REVISION, ION_AQUA, etc.). Hand-filtered to keep only real IOKit / NSSystemDirectories identifiers.

The 245-grep-count circulating earlier in the project's planning came from counting raw substring matches (every occurrence, including types and constants repeated across the file). The 49 number used in this spike is distinct function-call symbols, which is the meaningful unit for "does the CF source provide this?"

Raw audit documents (full per-symbol enumeration) are in the working tree:

Spike written 2026-05-15 against launchd-842 vendor drop in freebsd-launchd-mach at commit 0fae6a1 (LAUNCHD-BUILD-OK green). Audits performed against repo clones the same day; HEADs noted in each candidate section. Companion documents: the libxpc Foundation spike (project-level Foundation scoping), the launchd-842 porting plan (which scheduled launchctl as task I1e), and the install-layout spike (which puts launchctl at /bin/launchctl).