← 1.0.0 scope SAPE-23

InvokeVersion, so an older declaration is reachable

Status
Done
Version
1.0.0
Component
protocol, clients
Commits
2a05cab

Description

Declaring a name that is already declared writes a new version and leaves every older one stored and runnable — that is DeclareOperation's behaviour, and it is what makes an audit record naming an operation readable years later. The wire has carried a version field since before this ticket: internal/server's call struct decodes it and store.Store.Invoke takes it.

Nothing in either Go client could set it. So every version a redeclaration left behind was stored, readable, runnable by the engine, and unreachable by anybody holding the published package: the data was there, the declaration was there, and there was no way to ask for it.

Client.InvokeVersion(name, version, args) is added on internal/wire (internal/wire/wire.go:328) and forwarded on the public package (sapedb.go:152). Zero means the newest, which is what Invoke now asks for. The field is left off the request entirely when it is zero, matching the omitempty on the server's struct and on the TypeScript client's request body — sending an explicit 0 where every other client sends nothing is a difference with no meaning that somebody would one day have to explain.

Why it was in 1.0.0

It is a name on a published surface, and it is the only way to reach a feature the server already had. It is a second method rather than a third parameter on Invoke, because Invoke's signature is part of a surface this repository has already published; taking it back would break every caller for a field almost none of them pass. The two alternatives were worse: a variadic version ...int compiles for Invoke(name, args, 1, 2, 3) and reads in the documentation as something it is not, and an options struct means either a second method anyway or the same breaking change with more ceremony. One extra name is the honest price — and after a tag, adding it would cost a minor version rather than nothing.

How it was verified

  1. TestAnOlderVersionIsStillCallableThroughThePublicPackage (declare_live_test.go:321) declares three versions of one scan against a live daemon and tells them apart by row count — not by reading back the version number the server echoed, which would pass on a client that ignored the field.
  2. TestTheSurfaceIsExactlyTheseThirtyThreeNames (sapedb_test.go:44) lists InvokeVersion among the nine *Client methods, so it cannot be dropped or renamed silently. The guard's own name was renamed twice to match the number it guards.
  3. TestClientWrapperForwardsWithoutDroppingFields (sapedb_test.go:231) — the public package is a wrapper, and a wrapper that drops a field is the failure mode this catches.

The same work fixed a bug the wrapper test was the first thing to expose: Invoke sent its body under "arguments" where the server's call struct decodes "args". Nothing in this repository or its TypeScript sibling had ever invoked a declared operation through this client — the shell only calls Explore and WhatIsHere — so the server silently saw no arguments at all on every call.

What it did not do