Require policy and appAccess on createSpace #633

Closed ngerakines.me
targeting main
Closed opened by and assigned to ngerakines.me
targeting main

The lexicon marks both required; this server accepted their absence and applied the documented defaults, so a client sending neither got 200 and a silently defaulted space. A shipped client ran that way for months — invalid against the schema, accepted here, configured exactly as its owner intended — and was found by a reader comparing the request against the lexicon rather than by anything on the wire.

Proposal 0016 both gives the fields defaults (member-list / #open) and marks them required. Those are not in conflict: a default describes the resulting configuration, required describes the request. They are close enough that "absent means default" reads as correct while writing the handler, which is how this drifted, so the reasoning is now stated where the check lives.

What is required is the information, not a spelling. The lexicon's top-level unions, the nested config object, mintPolicy inside it, and the bare knownValues policy string all satisfy it; only "carries neither, anywhere" is refused, with 400 InvalidRequest naming both fields. Retiring the legacy spellings is a separate decision with its own deprecation window and is not taken here.

InvalidRequest and not UnsupportedPolicy / UnsupportedAppAccess: those name a well-formed variant this host cannot enforce, and a caller acts on them by choosing a different variant — the wrong instruction for one that sent no field at all.

The check is one arm on the merged config value create_space already computes, so it needs no second parse. updateSpace is unchanged: its optionality is deliberate, being a partial update. SpaceConfig::default() and from_create_input are unchanged too — the requirement is a property of the HTTP input, not of the config model, which has in-crate callers that legitimately construct a default.

One wrinkle: createSpace is idempotent on re-create, so a legacy client replaying a create it already made now gets 400 where it used to converge. That is the same refusal its first call would get today, so it is consistent rather than idempotency breaking.

This pull request has no versions submitted yet.