v0.17.4
Apiary 0.17.4 keeps answering MCP requests while Enclave is unreachable
The headline fix in this release is a wedge, not a crash. Apiary runs on a single cooperative scheduler, and a blocking connect to an unreachable Enclave took the kernel's entire SYN-retransmit budget, roughly 135 seconds, during which NO MCP request could be answered. Not even ones that touch no network at all. Measured against tools/list across a 240 second outage: 53 of 120 requests answered, with one contiguous dead window of about 134 seconds. A healthy control in the same session answered 20 of 20.
This release also stops Apiary reporting deliveries it cannot evidence, and moves the whole dependency set onto the Sigil 0.19 line.
The connect wedge
Apiary now opens the transport itself and hands a ready socket to sigil-irc, rather than calling irc-connect, which performs an unbounded connect with no way to pass a deadline. The TCP connect is bounded and the failure is classified, so a refused connection, a timeout, an unreachable host and an unreachable network are now distinguishable rather than all arriving as one generic failure. The whole call shares a single budget across every resolved address.
Boot-time retry ships in the same change and deliberately so. Before this release a failed connect at boot was terminal, so dispatch recovered once the process gave up: the wedge ended precisely when Apiary became permanently useless. Adding retry on its own would have converted a 134 second wedge followed by a dead but responsive process into one wedged for the entire outage. Bounding first and retrying second is safe only in that order, so the two ship together.
Delivery confirmation now waits for evidence, not for absence
Two defects in the Enclave bridge, both measured against an independent observer on a healthy connection.
The idle clock was reset by OUTBOUND traffic. A write succeeds into the local kernel send buffer and proves nothing about the peer, so a bot posting more often than the 60 second idle threshold kept resetting its own timer with writes that went nowhere, never sent a PING, and never noticed the link was dead. The busier the bot, the blinder it was. A matched pair frozen at the same instant, differing only in whether they posted, settled it: the silent one hit its PONG timeout at 96 seconds and recovered, the chatty one never fired at all. The clock is now inbound only, and renamed to say so.
Confirmation then waited for its token to LEAVE the outstanding list, and a token leaves for two very different reasons: the peer answered it, or we abandoned the socket. Absence conflated them, so a reconnect during the wait returned a successful broadcast for a message written to a socket being abandoned at that moment. That is the exact false positive the change exists to remove, reintroduced inside the fix for it, and it was caught by an adversarial pass rather than by the author. Confirmation now waits for positive evidence: a PONG handler records an exact token match in a separate list, and the waiter watches that. Confirming PINGs are also bound to the socket the message went out on.
The UNCONFIRMED string a calling agent receives was rewritten after reading it back as that agent. Every piece had been verified; the assembled sentence had not, and it stuttered in the middle. A test now asserts no rendering contains that seam.
Log entries can no longer be forged by the data they describe
Apiary logs channel and direct-message traffic, which is data other people control. A field value carrying a space and an equals sign used to render as several convincing fields, and a value carrying a newline forged a whole log event. sigil-log 0.17.0 quotes and escapes field values and sanitises field keys.
A value is quoted if and only if it contains a space, an equals sign, a double quote, a backslash, a control character or a Unicode separator, or is empty.
before nick=alice body=hello there status=sent
after nick=alice body="hello there" status=sentOrdinary values render exactly as before. If you parse these logs rather than read them, that difference matters.
Toolchain
The dependency set is aligned on Sigil 0.19, the packages reabsorbed into the Sigil monorepo were migrated, and sigil.lock was regenerated for the declared range. The repository also declares itself for the estate render.
Known regressions
None found. The transport failure harness added in this release exercises restart, freeze and abandonment paths off-device, without real Enclave traffic, so it covers the reconnect logic rather than a live outage against a real server.
Verification: 136 tests pass across 3 files, clean build. The release binary starts, and its own first log line shows the new rendering:
[INFO] Apiary starting mode=leader owner=<unset> reports-to=<unset> enclave="not configured"The two-word value is quoted while owner, reports-to and name stay bare on the same line, which is the conditional rule doing what it claims rather than quoting everything. The application's own compiled (sigil log) module also carries log-level-may-be-active?, an export that exists in sigil-log 0.17.0 and in no 0.16 release, checked against sibling modules of the same build and against a 0.16-built copy so the check can both fire and stand down.
The commit this tag names.