The renderer producing your policy input is changing shape — how do you version the contract and rules together?
answer
- the input is a versioned interface
- producer stamps, rules declare
- unknown version means no decision
- no decision must map to a block
- dual support with a deletion date
basics
~20 sStamp a schemaVersion on the input document and have each rule declare which versions it can decide over. On an unknown version the rule refuses to decide and the gate blocks, so drift is loud.
solid answer
~50 sTreat the input document as a versioned interface. The producer stamps a `schemaVersion`; every rule declares the versions it understands; meeting an unrecognised version the rule returns no decision, and the gate treats no decision as a block at the CI render step. That turns the dangerous failure - a rule that silently stops matching after a renderer upgrade - into a visible one somebody must resolve. When one team owns producer and rules, bump the version and update the rules in the same change. When two teams own them, rules accept old and new for a bounded window, the producer cuts over, then old support is deleted on a date. The judgment call: refusing to decide blocks every build the moment the renderer moves, but a rule quietly enforcing nothing for a quarter costs more.
go deeper
Know that the document a rule reads can change shape, and that a version marker on it is how the rule can tell.
Explain the four moving parts: the producer stamps a version, rules declare what they handle, an unknown version yields no decision, and the gate turns no decision into a failure.
Be ready to argue why refusing to decide beats best-effort matching, and to describe the dual-acceptance window you would run when the producer is owned by another team.
Own the trade-off publicly: fail-closed on an unknown shape can stop an estate's builds, and you should be able to say which rule classes get that treatment, who resolves a mismatch, and what evidence shows the control was live throughout.
## The failure this design prevents A rule that no longer matches its input is the worst outcome available to a policy programme, because it looks exactly like success. Builds go green, the gate reports zero violations, the control shows as passing, and nothing enforces anything. Every other failure — a false denial, an outage in the engine, a noisy message — announces itself. This one does not, and the trigger is usually mundane: somebody upgraded the rendering chain and the output moved. Versioning the input contract is how you buy a signal. ## The mechanism 1. **The producer stamps the shape.** The document (or the envelope around it) carries a `schemaVersion`. It describes the *contract*, not the application, and it moves when the shape moves: a field renamed, a list becoming an object, a value's encoding changing. 2. **Rules declare what they can decide over.** Each rule, or the rule set as a whole, names the versions it handles. 3. **Unknown version means no decision.** The rule does not guess. It returns nothing and says why. 4. **The gate maps no-decision to a block.** At the CI render step, before any cluster sees the chart, an undecidable input fails the job with a message naming the version it received and the versions the rules support. That fourth step is the one people skip, and skipping it undoes the other three: a rule that abstains into a gate that only reacts to explicit denials is a rule that has been silently disabled with extra ceremony. ## Why refusing to decide beats best-effort matching The tempting alternative is to keep evaluating on whatever fields are recognised. It feels resilient and it is the trap: the rule now enforces a subset of itself, chosen by accident, with no signal about which part. A partial gate that reports success is more dangerous than no gate, because it is trusted. Refusing is honest — the rule is saying *I cannot evaluate this document*, which is true. ## The organisational half The mechanism is easy; the rollout is the actual question, and it depends on who owns what. **One team owns both.** Ship the producer bump and the rule update in one change. The contract, the fixtures and the rules move together and review sees the whole thing. This is the reason to keep the contract in the rules' repository even when the producer is elsewhere. **Two teams own them.** You cannot land one change, so you buy a window. Rules learn the new version first and accept both; the producer cuts over; after an agreed date, old-version support is deleted rather than left in place. Deleting matters: dual support that never ends becomes two shapes to test forever, and the second shape is the one nobody re-checks. **Many producers.** With an estate of teams rendering their own charts, a hard cutover blocks everybody at once. Then the sequencing is: announce with a date, make the version check warn while it counts how many documents still arrive on the old shape, and flip to blocking when that count reaches zero or the deadline lands — a decision made on the count, not on a feeling. ## The trade-off to state out loud Refusing to decide is fail-closed, and fail-closed on a shared pipeline means one renderer upgrade can stop every team's build. That is a real cost and a real conversation with the platform team, not a detail. The counterweight is what the alternative costs: an enforcement gap of unknown size and unknown duration, discovered — if at all — by an incident or an audit sample. For a control that exists to keep credentials out of process environments, a loud build failure with a clear message and a named owner is the cheaper failure. For a low-severity advisory rule, the same argument does not hold, and warning is the right response to an unknown version. The answer an interviewer wants is that you made this choice per rule class rather than globally, that somebody is on the hook for resolving a version mismatch quickly, and that you can say what the rules were doing during the window. ## Evidence A versioned contract has a side benefit worth naming: it makes "was this control actually running" answerable. The record of which contract version was in force, and which rule versions were deciding over it, is a far better answer to that question than a year of green checkmarks — because green checkmarks are exactly what a silently-disabled rule produces.
- Why not have the rule fall back to best-effort matching on the fields it recognises?Because it then enforces an arbitrary subset of itself while continuing to report success. Nobody can say which part of the policy is live, and the gap is invisible. Refusing to decide states the truth — the document cannot be evaluated — and forces a person to look. Best-effort matching is how a rule ends up enforcing nothing for a quarter.
- Rules and renderer are owned by different teams. How do you sequence the change?Teach the rules the new version first so they accept both shapes, then let the producer cut over, then delete old-version support on an agreed date. Never merge the producer change first: that opens a window where the rules recognise nothing. If many teams render independently, warn while counting documents still on the old shape and flip to blocking when the count hits zero or the deadline arrives.
- Fail-closed on an unknown version can stop every team's build. When is warning the better call?When the rule is advisory rather than a hard control, or during an announced migration window where you have a count of remaining old-shape documents and a date. For a control that keeps credentials out of process environments, block: an enforcement gap of unknown size costs more than a build failure with a clear message and an owner on call.
saying these in an interview costs you the question
- Falls back to partial matching on an unrecognised document
- Lets the rule abstain into a gate that only blocks explicit denials
- Merges the producer change before the rules understand it
- Keeps dual-version support forever with no deletion date
- Treats a renderer upgrade as irrelevant to policy