feat: require every v2 interface to declare who serves it - #10
Conversation
With the official manifests re-signed to declare their backing, the host no longer needs to infer anything, so the inference is gone. A v2 manifest that declares no backing is now rejected at load rather than resolved by guesswork. That is deliberately louder than the alternative: a silently-inferred service fails somewhere downstream, at call time, with an error that describes a symptom rather than the cause. Refusing the manifest outright names the real problem — this plugin never said who serves its methods — and it fails closed, so a plugin cannot be loaded on a promise nobody checked. The publisher-string heuristic goes with it. Who serves a method is now settled entirely by the signed manifest, and by nothing else. Deployment order matters: a server carrying this change refuses a manifest that predates `backing`, and a server that predates `backing` refuses a manifest that carries it (the loader disallows unknown fields). Ship the server first, then the re-signed plugins. Tests: go vet clean; internal/plugin and internal/server green.
|
[ack] — zeus review, Olympus TASK-0001 item 2 (stacked half) (2026-07-26) Clean deletion of the inference path; undeclared-backing v2 manifests rejected at load with a cause-naming error. The two-sided deployment order was executed against production 2026-07-15 (additive #8 server → re-signed plugins → this). Standing constraints now on record in Olympus: fresh nodes bootstrapping from pre-backing bundles fail closed (correct), and a server rollback past #8 strands every re-signed plugin — rollback floor is alpha-0.2.2. Evidence: contained in |
|
Closed as landed — zeus, operator-approved sweep (2026-07-26, rules/01 §8.5): commit |
With the official manifests re-signed to declare their backing (vpn-core#2, netguard#2, wireguard#2, sub-store#2, template#2), the host no longer needs to infer anything — so the inference is gone.
What changes
backingis now rejected at load.Publisher == "latticenet"heuristic is deleted from the dispatch path. Who serves a method is settled entirely by the signed manifest, and by nothing else.Why reject rather than infer
A silently-inferred service fails somewhere downstream, at call time, with an error describing a symptom rather than a cause. Refusing the manifest names the real problem — this plugin never said who serves its methods — and it fails closed, so a plugin cannot be loaded on a promise nobody checked.
This is a two-sided compatibility break, and it is symmetric:
backing.backingsupport refuses a manifest that carries it (the loader usesDisallowUnknownFields→json: unknown field "backing").Both fail closed — the plugin does not load — so neither direction degrades silently. But the sequence is: deploy the server (#8, which accepts both), then the re-signed plugins, then this.
Test plan
go vet ./internal/...cleango test ./internal/plugin/ ./internal/server/ -race— green (736s under load)TestBackingValidationextended: a v2 interface with no backing is rejectedinternal/pluginandinternal/serverupdated to declare backing — the fixture churn is the proof that the rule now bindshttps://claude.ai/code/session_01FzYExyy6G7Cb7QpXoUEY78