Design 16: Sub-Store as a native subscription platform - #5
Draft
lr00rl wants to merge 4 commits into
Draft
Conversation
The operator's goal changed: shut the standalone Sub-Store down rather than integrate with it. That reverses design-substore-embed's §7 answer, which scoped v1 to conversion only, so the reversal is recorded rather than left implicit. Twenty-one upstream route modules are several subsystems, so this splits into six sub-projects and specifies only the first: the subscription store and the public distribution path. It goes first because it is the foundation and the only part touching the core server, and because a feature-complete converter nobody can subscribe to does not let anyone switch the old instance off. The load-bearing decision is that the core keeps the entire public surface -- route, token, rate limit, audit, headers, cache -- and the plugin answers one question: given a subscription id, a format and a client UA, produce content. A general http:serve capability was rejected for handing token checking and rate limiting to plugin code, which is the shape the plugin-boundary review already turned down. Two rules exist because their opposite is the tempting default: a subscription response is never an empty body with HTTP 200, because a client that gets one deletes every node it had; and the last successful remote snapshot is durable rather than cached, because it is the only thing that keeps clients served when a provider goes down. Verifying the constraints turned up two pre-existing defects, recorded in §10 with their own tasks and depended on by nothing here: Broker.KVPut accepts values of unbounded size, and kv/static still ride the full-rewrite state.json path despite having bolt buckets.
The snapshot is produced by plugin-side fetching, so bolt was never reachable for it. Each plugin does get a writable confined working directory, which is where it belongs. Recorded before any code is written against the wrong home, and sub-project 2 owes a test that content there survives a re-arm.
A plugin has no durable storage that is not the state file. Bolt is unreachable from a plugin, and the working directory is deleted by SystemRunner.Stop by design -- its own comment says it removes the runtime dir. So the core owns the snapshot as an opaque blob and the plugin stays stateless: it fetches on request, hands the bytes back, and is given them again on the next render. Both earlier answers stay in the row rather than being quietly replaced, because the reason each failed is the useful part.
The first implementation leaked five ways whether a token was valid, the sharpest being a 400 for a bad format after the token had already been resolved. Every rejection now returns one fingerprint-free response, format is validated before resolution so ordering cannot leak, and the rate limiter refuses in the same voice instead of announcing a specially-limited path. Truth moves to the audit log rather than disappearing. Two things are recorded as deliberately not done: the server does not proxy to a decoy, because that disguise belongs at the reverse proxy and would be imperfect here anyway; and timing remains distinguishable, because equalising it costs a worst-case delay on every rejection for an imperfect result.
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.
Specifies sub-project 1 of 6: the subscription store and the public distribution path.
Why this exists
The operator's goal moved from "integrate with a standalone Sub-Store" to "shut it down". That reverses
design-substore-embed.md §7open question 2, which had scoped v1 to conversion only — so the reversal is written down rather than left implicit.Acceptance is deliberately not feature parity. It is: the operator can stop the standalone instance and every client that subscribed to it keeps working. That ordering puts distribution and migration ahead of operator breadth.
The load-bearing decision
The core keeps the entire public surface — route, token lookup, slug comparison, rate limit, audit, Content-Type,
Subscription-Userinfo, output cache. The plugin answers exactly one question: given a subscription id, a format and a client UA, produce content.A general
http:servecapability was considered and rejected: it hands token checking, rate limiting, audit and response headers to plugin code, which is the shape the plugin-boundary review already turned down.Two rules that exist because their opposite is the tempting default
ignore-failed-remote-subbehaviour.Constraints were verified, not remembered
§3 records each one with its
file:line. Two of them turned out to be pre-existing defects, recorded in §10 with their own tasks; nothing in this design depends on either being fixed first:Broker.KVPutenforces capability and key shape but no value size limit — any signed plugin withkv:writecan growstate.jsonwithout bound, and that file is rewritten in full and fsynced on every state write.kvandstaticare absent from the bolt hot-store exclusion list, so both still ride the full-rewrite path despite having bolt buckets.Also verified and worth noting: the single-segment
/sub/<token>form can be removed outright because the deployment has zero proxy users, profiles and inbounds — no live subscription breaks.Draft until the operator reviews the spec.