Repository navigation
refactor(protocol): move the wire format into its own Apache-2.0 module - #1588
Merged
Merged
Conversation
FTW Cloud must speak the box protocol without linking the box. Today the wire types share package appproto with the handler, which imports control, loadpoint, mpc and config, so a service that imports them links 340 packages and sqlite. go/protocol is a nested module, github.com/srcfl/ftw/go/protocol, under Apache-2.0. It holds appwire whole, the envelope, the message shapes (without wireModes), the generated registry names, the history tile geometry with the packing from buildTile, and the push catalogue. It requires only cbor and x/crypto. The box requires it through a replace directive; there are no tags yet. Aliases and one-line wrappers keep every old name in appproto and notifications, so no call site changes. Five files import appwire from its new path. The generator stays in the box and writes into go/protocol. CI and make verify run the module's own vet and tests, and a go list -deps check fails if it reaches any box package. Go's internal rule does not stop that import, because the module's path sits under the box's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
FTW Cloud (C1 onward, own repo
srcfl/ftw-cloud, AGPL) must speak the box protocol. Today the wire types share packageappprotowith the handler, and the handler importscontrol,loadpoint,mpcandconfig. A service that imports the wire types therefore links the box: control code, the planner and sqlite. This PR moves the wire format into its own module so that "the cloud never controls" holds in the build graph, not only in a test. It is step C1a; ADR 0008 is written in a parallel session.What moved
New nested module
github.com/srcfl/ftw/go/protocolingo/protocol, Apache-2.0, requiring onlyfxamacker/cborandx/crypto:go/protocol/appwire/(all 10 files, testdata included)go/internal/appwire/go/protocol/envelope.go,envelope_test.gogo/internal/appproto/newEnvelopeandbulkBucketForexported asNewEnvelopeandBulkBucketForgo/protocol/messages.gogo/internal/appproto/wireModesstays in the box (appproto/modes.go)go/protocol/contract_gen.gogo/internal/appproto/go/protocol/tiles.go,tiles_test.gogo/internal/appproto/history.go,history_test.goHistorySample, and the packing half ofbuildTileasPackTilego/protocol/push.go,push_gen.go,push_test.gogo/internal/notifications/catalogue*.gogo/protocol/contract_test.goTestFrozenFieldIdsAreFrozenfromappproto/contract_test.goThe box keeps every old name:
appproto/protocol.gore-exports the moved types, constants and values as aliases, plus one-line wrappers for the four functions.notifications/catalogue.godoes the same for the push catalogue.appwirechange only their import path.buildTilereads the store and callsprotocol.PackTile. The planner (planHistoryQuery) stays in the box.plannedTile.pointswas read only bybuildTile, so it is gone.protocol:instead ofappproto:ornotifications:. No test or client reads it.The generator stays in the box at
go/internal/appproto/gencontract, and its output moves:go:generatelines writego/protocol/contract_gen.goandgo/protocol/push_gen.go. It also writesapiauth/contract_gen.go, which stays in the box.go generate ./internal/...fromgo/regenerates all three, as before.yaml.v3to that module and make the generator its public API, only so thatapiauthcould reach it.The box requires the module through
replace github.com/srcfl/ftw/go/protocol => ./protocolingo/go.mod. There are no tags yet; they come after C3.Licence
go/protocol/LICENSEis the Apache-2.0 text, byte-identical tosrcfl/ftw-webapp/LICENSE(sha256cfc7749b…).NOTICEandLICENSING.mdeach gain a short paragraph that names the exception. The rest of the repository stays AGPL-3.0-only.Fredrik approved on 12 Oct 2026 that the parts he wrote are relicensed to Apache-2.0 ("de är mina").
Which files change licence, and why:
d281df8c(2026-09-12)git blameappwire/*(10 files)envelope.go,envelope_test.gobuildTilepacking (nowtiles.go), the two moved geometry testspush*.go)TestFrozenFieldIdsAreFrozenmessages.goOpPlannerPrefsSet(1853d674, 2026-09-29)contract_gen.go(generated)1853d674, Cursor Agent), −2 lines (a71f777c, Fredrik)contract/registry.yamlPackTilesignature,NewEnvelope,BulkBucketForexports,TestPackTileFoldsMeansAndMarksHoles, test helpersNOTICEkeeps available. For those files the change only extends Apache-2.0 to the current text.contract/registry.yamlandcontract/push-catalogue.yaml. Those are edited insrcfl/ftw-webapp, which is Apache-2.0.A lawyer should confirm that the copyright in these files belongs to Fredrik or to Sourceful Labs AB, and so can be relicensed. They should also confirm how to treat the 6 lines committed by Cursor Agent (with a
Signed-off-by: Cursor Agent), and the code written by Claude in this PR.LICENSING.mdalready says that a DCO sign-off is not a copyright assignment.Proof the module builds without the box
A test module outside the repository, with an unrelated module path (
example.com/ftwcloudprobe, assrcfl/ftw-cloudwill have), importsprotocolandprotocol/appwire. It starts a Noise IK responder, encodes asnapenvelope, frames it, packs a tile and renders a push sentence. The "before" probe makes the same calls against851eaee0. It needs a module path undergithub.com/srcfl/ftw/go/to be allowed to importinternal/.Built with the release flags (
CGO_ENABLED=0 -trimpath -tags netgo,osusergo -ldflags '-s -w'), Go 1.27.1, linux:go list -deps)internal/appproto+internal/appwire+internal/notificationsgo/protocol+go/protocol/appwireThe "after" binary's module list (
go version -m) contains onlyfxamacker/cbor/v2 v2.9.4,x448/float16 v0.8.4,golang.org/x/crypto v0.57.0andsrcfl/ftw/go/protocol.Probe source (after)
go.mod:module example.com/ftwcloudprobe,require github.com/srcfl/ftw/go/protocol v0.0.0-00010101000000-000000000000,replaceto the checkout.The import ban, and its test
.github/scripts/check-protocol-boundary.shfails whengo list -deps -test ./...ingo/protocolreaches anygithub.com/srcfl/ftw/go/...package outside the module, or whengo list -m alllists the box module.test.yml(core job) runs it after the module's owngo vetandgo test.make verifyruns it asmake protocol-boundary.The check is needed: Go's
internalrule does not block this import, becausegithub.com/srcfl/ftw/go/protocolsits under the box's path.internal/controland arequire/replaceof the box. It compiles and vets, and the check exits 1. It lists 11 box packages underIt reaches:(internal/control,events,loadpoint,mpc,stateand others) and the box module underIt requires:.go/protocol: 121 packages, none from the box.The box builds as before
Release flags as above,
./cmd/ftw:851eaee0851eaee0The one extra package is the new
protocolroot package.Other build paths:
Dockerfile:go mod downloadreadsgo/protocol/go.modthrough thereplace, so the dependency layer copies it as well. Without that line the layer fails withreading protocol/go.mod: no such file or directory; I reproduced that in a temporary directory, and it passes with the line.go/tools/history-migrate: needs no change.go mod tidy -diffis clean andgo vet ./...passes, because graph pruning never loads the protocol requirement there./goand/go/protocol.Tests
cd go/protocol && go vet ./... && go test ./...: green.appproto,notifications,apiauth,appuplink,appenroll): green.make verifyon this branch: green, exit 0. 69 Go packages reportok, including both protocol packages, and the boundary check passes. Master's known red test,TestDispatchReachesEveryInstalledPublisher, passed in this run. It fails by chance on a race in the test itself (fixed in test(notifications): wait for the dispatch event, not the fake transport #1587), and this PR does not touch that path.Generated files:
go generate ./internal/appproto/ ./internal/notifications/ ./internal/apiauth/writes the committed bytes. Against master, the only difference is the package clause.Not in this PR
appuplink/codec.gostays in the box. It is the 49-lineFrameCodecthat joinsprotocol.Frametoappwire, and FTW Cloud will need one too. Moving it means eitherappwireimportsprotocol, or a third package. That is a design choice for C2.sourceState,wireSources,fieldDict,sourcesEqual) andplanHistoryQuerystay in the box. The task list did not include them; core report C8 covers exporting them.contract/README.md, a comment incontract/registry.yaml(byte-paired with ftw-webapp), anddocs/architecture.md:368. The text has gone to the session that owns those files.No paired PR is needed: no name in
registry.yamlchanges. Labelno-changeset, because users see no change.🤖 Generated with Claude Code