docs(decisions): add a decision record log alongside design proposals - #56
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughThe change adds decision-record documentation, an ADR template, an accepted ComputePlane architecture decision, and guidance for linking decisions to design proposals. ChangesDecision records and proposal guidance
Estimated code review effort: 2 (Simple) | ~10 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Timofei Larkin (lllamnyp)
left a comment
There was a problem hiding this comment.
Review: decision records
The diagnosis is right, and the evidence for it is the strongest part of this PR. Design proposals here are intent, written before the work; when implementation contradicts them we edit them in place, so they end up reading as though they always said the current thing. #42 and #44 rewrote the database-horizontal-autoscaling proposal after a spike; #53 reopens its actuation mechanism on a CloudNativePG finding; #40, #41 and #36 are the same shape. The compute-plane proposal carries a Revision (this PR): paragraph in its metadata block because that rationale had nowhere else to live. Something should exist for this, and the writing throughout — the proposal-vs-record table, the "when not to write one" list, the immutability rule — is better than most first drafts of a process.
I want one structural change before this lands, plus a set of corrections. Taking them in the order I think they matter.
1. Records should live with the proposal they amend
Concretely: design-proposals/<name>/decisions/NNNN-slug.md, with the process text folded into design-proposals/README.md and the template at design-proposals/decision-template.md, instead of a root-level decisions/ tree.
The argument that convinces me is the one your own template raises and then waves through. **Proposal:** ... — or none presupposes that a decision can exist here without a proposal. I don't think it can. An architectural decision is a decision with respect to something; this repo's front-door table already routes every candidate — cross-cutting architectural change to a design proposal here, bug or scoped feature to cozystack/cozystack, governance to an issue here. There's no residue. And a deliberation weighty enough to need a record, with no proposal to attach it to, has just demonstrated that it is a proposal.
Once that holds, the rest follows:
- Both link directions come for free. A reader browsing the proposal directory sees its decisions; a record's parent directory is its proposal. Nothing to maintain, and hand-maintained backlinks are the first thing to rot. The
Decisionssection added todesign-proposals/template.mdbecomes unnecessary rather than load-bearing. - Numbering stops colliding in practice. This repo routinely has five or so PRs open at once, so a global sequence collides constantly, and "whoever merges second renumbers on rebase" breaks any citation already written down. Per-proposal numbering collides only when two PRs touch the same proposal.
- No new top-level concept. The root README is a routing table; a
decisions/tree adds a second document class to it, and the new "a decision already made" row is slightly false — you never file a decision on its own, you file a proposal amendment that carries one. Per-proposal placement extends the process that exists and adds nothing to the front door. - Withdrawn proposals take their decisions with them instead of orphaning entries in a global log.
The case for the root log deserves recording, because parts of it are real:
- A monotonic id is short, citable and survives a directory rename. True, and the cost of giving it up is real; per-proposal files stay numbered, and citations become
compute-plane ADR 0002— more verbose, more informative. - Nygard's convention and its tooling assume one log — adr-tools defaults to a single
doc/adr. Real, but nothing in this repo would consume it: there is no CI here at all, and this template already departs from Nygard's four sections. - "What did we decide lately?" — answered in any layout by
git log -- 'design-proposals/*/decisions/*', better than by directory order. - Decisions that span proposals have no single home. This is the one that nearly carried it for me, and the answer is section 4 below.
2. This PR should introduce itself through the mechanism it introduces
As it stands, the reasoning for why decision records exist and where they live will survive only in this PR thread — which is precisely the failure the PR is written to fix. It reproduces its own diagnosis on itself.
So I'd like this to land as:
- A design proposal —
design-proposals/decision-records/README.md— stating what is being introduced and why, with the drift evidence already in your PR description. Note that a merged proposal is "a reference, not a binding spec", so the operative rules (when a record is required, the template, the immutability rule) still need to land indesign-proposals/README.mdand the template; the proposal states the change and the rationale, not the law. - A decision record under it —
design-proposals/decision-records/decisions/0001-decision-records-live-with-their-proposals.md— recording the placement decision and, in theWhy not the alternativessection, what lost: the root-level log, with the four points above stated at their real strength rather than strawmanned.
This isn't ceremony. It gives the format an exemplar written by the person who owns it, from an argument they were present for, rather than leaving 0001 — a backfill of an argument between other people — as the only pattern every future author copies. It also demonstrates per-proposal numbering immediately: two proposals, two records, both numbered 0001.
3. Corrections to the ComputePlane record
You asked me to check the Deciders line. The handle you didn't want to guess is mine, @lllamnyp — but I'd change more than that.
- The record never cites #33. It links #17, #26, #27 and #39, but not the PR where the decision was actually argued and merged, which is also where its
2026-07-18date comes from. A record whose job is to be the durable pointer to the reasoning points everywhere except at it. Add aDecided in:field to the template — that would have caught this mechanically. - The #17 attribution is wrong. The record says two reviews converged on the objection that ComputePlane should not be a distinct kind, citing #26 and my review on #17. My #17 review was an approval, and its points were: that "hidden from the tenant" was conflated with the security boundary, and the real justification for withholding access is tamper-resistance — withhold admin, not visibility; that the "1-click" framing didn't distinguish from the rejected alternative and
placementshould target a named cluster; that letting child tenants inherit an ancestor's ComputePlane re-creates the problem ComputePlanes exist to solve; and that there was no story for ComputePlane workloads reaching tenant-namespace services. The "not a distinct kind" argument is #26's alone — #26 quoted my approval summary in support of its own case. What that review did contribute is the tamper-resistance argument, which is the load-bearing rationale of this record's own Decision and Consequences sections, and it's credited to nobody. Decidersshould be@kvaps, @lllamnyp— the author of #33 and the maintainer who approved it. myasnikovdaniil's #26 forced the reopening and deserves the credit in Context, but its literal ask (no new kind at all) is not what was adopted, as the record itself says.Implemented in: not yetis false. cozystack/cozystack#3280 merged 2026-07-29.- The rejection of the preset-field model rests on the fragile argument. The record justifies the shape through
packages/extra/computeplaneand the PackageSource convention. The durable argument is structural and survives #39 deleting theextracategory: capabilities and schemas are per-kind, so two postures over one chart need two ApplicationDefinitions;ApplicationDefinitionSpechas no fixed-values facility and the conversion makes the tenant's spec become the Helm values, so operator-fixed values have nowhere to live but a chart; and a narrowopenAPISchemadoesn't substitute, because a field you can default is a field the tenant can override, while a field absent from the schema receives the chart's own unhardened default. "Hardened and unreachable" isn't expressible in a schema. - A dropped consequence. #33 was asked to co-land with #39, or land after it, because once tenants set
computeplane: truethe release-name invariant stops being a markdown disagreement and becomes a migration of live Kamaji clusters holding tenant data. #3280 has since merged and #39 is still open, so that constraint is live now. The record presents #39 as purely composing. - Minor: #27 is cited as a revision without noting it was closed unmerged, superseded by #33 the same day. In a document that is explicitly a history, that matters.
4. Process amendments
- Drop
— or nonefrom theProposal:field. Per section 1, make it required. - Add
Decided in:for the PR or issue thread where the decision was argued, and one line underWhy not the alternativesasking that each rejected option be sourced to the comment or PR it came from. Both of the substantive errors above come from writing alternatives from memory, and accuracy is checkable only if the source is linked. - Separate metadata from body in the immutability rule. "The body of an accepted record is not edited" is right, but
Status,Superseded byandImplemented inmust track reality — the lifecycle depends on it. Say that the header block is maintained and the prose below it is frozen. - Record what belongs in code instead. Where a decision's content is a fact about a type, a field or an invariant that code must respect, its primary home is a comment at that site plus a test that fails when it's violated; the record links to it rather than restating it. This project already does this well: the
computeplanerelease-name invariant is enforced atpackages/extra/computeplane/templates/check-release-name.yamland pinned bytests/release_name_test.yaml, whose suite comment carries the mechanism. That guard is a more reliable record than any prose, and it stayed more precise than this record's version of the same invariant. Without this rule the log fills with restatements of things the code already enforces. - Enforcement. There is no CI in this repository —
.github/contains only issue templates — so the rule in "Proposal Drift" is only as binding as the place a reviewer looks. Add.github/pull_request_template.mdwith one checkbox: this PR revises a merged proposal; it adds a record under that proposal'sdecisions/, or says why none is needed. That is the only enforcement available, and it costs one file. - Decide what happens to the existing in-proposal narratives. Compute-plane's
Revision (this PR):metadata line and the database-autoscaler'sWhy this changedsection plus spike appendix are three improvised solutions to this same problem, and this PR adds a fourth without retiring any. My preference is extraction, especially forRevision (this PR):, which is unmaintainable by construction — "this PR" stops resolving the moment the next revision lands. - The review bar is a governance change. "One maintainer's approval is enough" against the proposal process's "consensus from core maintainers" is probably right for accuracy-checking a settled decision, and the reasoning given for it is sound — but a lighter approval bar for a new document class should be visibly agreed rather than merged as documentation.
5. A second record, from a different subsystem
One backfilled record isn't enough to show the mechanism works, and a log that ships at 0001 and stops is the standard failure. The live candidate is the database-horizontal-autoscaling proposal: rev1's bespoke db-autoscaler operator was rejected after an implementation spike proved its enforcement premise unbuildable on the aggregated apps API. That decision is settled and merged, its rationale currently survives only in a proposal appendix that the immutability rule doesn't protect, and #53's CloudNativePG finding has since reopened the actuation mechanism downstream of it — so it would exercise Revisit if on live content rather than a hypothetical. Either land it here or open the ask on #53; I'd rather not merge the mechanism with one instance.
6. Follow-up: where design principles live
Some of what surfaced around ComputePlane isn't a decision about ComputePlane at all — that ApplicationDefinition has no operator-fixed-values facility, and that settability and defaultability are the same property in a structural schema, are standing constraints on every operator-owned module anyone builds next. A record filed under one proposal is the wrong home for them, and today they're recorded nowhere: ApplicationDefinitionSpec in api/v1alpha1/applicationdefinitions_types.go carries no such comment, and the conversion at pkg/registry/apps/application/rest.go is a bare Values: app.Spec.
Code comments plus tests cover the enforceable half, but there is genuinely no home in Cozystack for a design principle as such — something like an architecture or principles document in cozystack/cozystack. That's out of scope here, but please open an issue for it and link it from the proposal in section 2, so the gap is recorded rather than absorbed into this log by default.
To be explicit about cost: I'm asking for a restructure of a PR that's already complete, and I don't think that's free. But the move is mechanical now — one git mv, one prose fold, four link fixes — and it stops being mechanical once the log has ten entries and citations pointing at it from other repositories. Everything in section 3 is independent of the placement question and needs doing either way.
The reasoning for why decision records exist and where they live was going to survive only in the #56 thread — which is precisely the failure the change is written to fix. It reproduced its own diagnosis on itself. So it lands as a design proposal, design-proposals/decision-records, with the drift evidence that was previously only in the pull-request description, and a record under it for the placement decision. The proposal states the change and the rationale; the operative rules stay in design-proposals/README.md, because a merged proposal is a reference and not a binding spec. This is not ceremony, on one condition: the record has a losing alternative. "We will write decision records" has none, and a record for it would be a form to copy. The placement question has four real arguments on the other side, and 0001 states them at full strength — the citable monotonic id, Nygard's tooling, the single directory that lists itself, and cross-proposal decisions having no home — then says what decided each one. It also demonstrates per-proposal numbering immediately: two proposals, two records, both 0001. Recorded a fifth alternative that the review did not raise and neither did the original proposal: freezing merged proposals and superseding them, the Rust RFC and Python PEP model, where the history is automatic because supersession is the only way to change anything. Rejected — it contradicts "a reference, not a binding spec" and makes every implementation finding a new proposal — but it is the model the largest peers picked, and Kubernetes keeping living KEPs has exactly this gap and fills it with milestone dates only. A future reader will ask why we did not do that, so the answer is on the record. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
|
Restructured to per-proposal, section 3 fixed, 4 and 5 done.
One thing neither of us raised: rust and python freeze merged proposals, so supersession is the only way to change anything and the history is automatic. Kubernetes keeps living KEPs like we do and has the same gap, its Implementation History records dates not reasoning. I recorded freezing as a rejected alternative in 0001 because it contradicts "reference, not a binding spec", but it is the real fallback if records don't get written. Folded #57 in here, it was one line in a file this branch already touches, so closing it. |
Design proposals are intent, written before the work. When implementation contradicts the design the proposal gets revised in place, so it ends up reading as though it always said the current thing -- and the reasoning that changed our minds survives only in a pull-request diff. Add decisions/ for that reasoning: one page per settled decision, never edited after merge, superseded rather than rewritten. Reviewed for accuracy by one maintainer rather than by consensus, because the decision has already been made. Wire it into the "Where do I file?" table and rewrite Proposal Drift to say what to do when the implementation diverges. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The ComputePlane delivery shape was decided three times: a tenant module with an inline cluster HelmRelease (#17), preset fields on kind: Kubernetes (#27, closed), and finally an operator-owned extra module wrapping the unchanged apps/kubernetes (#33). Only the last one is visible in the proposal today; why the preset-field model lost lives in the #33 body. Record it as the first decision, and add the Decisions section to the proposal template so later records get linked from the proposal they amend. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Records lived in a root-level decisions/ tree with a global sequence. Move them to design-proposals/<name>/decisions/NNNN-slug.md, fold the process text into design-proposals/README.md, and put the template at design-proposals/decision-template.md. A decision is a decision with respect to something, and the front-door table routes every candidate that is not a proposal amendment elsewhere — so a deliberation weighty enough to need a record, with no proposal to attach it to, has demonstrated that it is a proposal. The template's "Proposal: ... or none" presupposed a residue that does not exist; the field is now required. What follows from the placement: both link directions come for free, so the proposal's Decisions section stops being load-bearing and the record's parent directory is its proposal. Numbering collides only when two pull requests touch the same proposal, rather than constantly against a global sequence whose renumber-on-rebase rule breaks any citation already written down. A withdrawn proposal takes its decisions with it instead of orphaning entries in a global log. And the root README goes back to routing two document classes instead of three — you never file a decision on its own, you file a proposal amendment that carries one. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The record was backfilled from an argument between other people and got several things wrong. Corrections, all verified against the threads and the code: Deciders is @kvaps and @lllamnyp — the author of #33 and the maintainer who approved it. @myasnikovdaniil's #26 forced the reopening and is now credited in Context, where it belongs; its literal ask (no new kind at all) is not what was adopted. The record never cited #33, the pull request where the decision was actually argued and merged, and where its 2026-07-18 date comes from. A record whose job is to be the durable pointer to the reasoning pointed everywhere except at it. It is now in the new Decided in field. The #17 attribution was wrong. That review was an approval, and it did not argue that ComputePlane should not be a distinct kind — that is #26's argument alone, which #26 made while quoting the approval in support. What the review did contribute is the tamper-resistance argument (withhold admin, not visibility), which is the load-bearing rationale of this record's own Decision and Consequences and was credited to nobody. Implemented in was "not yet"; cozystack/cozystack#3280 merged 2026-07-29. The rejection of the preset-field model rested on the fragile argument — packages/extra/computeplane and the PackageSource convention, both of which #39 would dissolve. Replaced with the structural one, which survives it: capabilities and schemas are per-kind, ApplicationDefinition has no fixed-values facility, the conversion makes the tenant's spec become the Helm values, and a narrow openAPISchema does not substitute because a field you can default is a field the tenant can override. The release-name invariant is now a link to the guard and its test rather than a restatement. The prose said it constrained moving the chart between directories; the actual mechanism is that the aggregated API rebuilds HelmRelease specs without spec.releaseName, which the test's suite comment states correctly. Added the dropped consequence: #33 was asked to co-land with #39 or land after it, because once tenants can set computeplane the invariant becomes a migration of live Kamaji clusters holding tenant data. #3280 has merged and #39 is open, so that constraint is live now rather than composing. Noted that #27 was closed unmerged, superseded by #33 the same day. In a document that is explicitly a history, that matters. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The reasoning for why decision records exist and where they live was going to survive only in the #56 thread — which is precisely the failure the change is written to fix. It reproduced its own diagnosis on itself. So it lands as a design proposal, design-proposals/decision-records, with the drift evidence that was previously only in the pull-request description, and a record under it for the placement decision. The proposal states the change and the rationale; the operative rules stay in design-proposals/README.md, because a merged proposal is a reference and not a binding spec. This is not ceremony, on one condition: the record has a losing alternative. "We will write decision records" has none, and a record for it would be a form to copy. The placement question has four real arguments on the other side, and 0001 states them at full strength — the citable monotonic id, Nygard's tooling, the single directory that lists itself, and cross-proposal decisions having no home — then says what decided each one. It also demonstrates per-proposal numbering immediately: two proposals, two records, both 0001. Recorded a fifth alternative that the review did not raise and neither did the original proposal: freezing merged proposals and superseding them, the Rust RFC and Python PEP model, where the history is automatic because supersession is the only way to change anything. Rejected — it contradicts "a reference, not a binding spec" and makes every implementation finding a new proposal — but it is the model the largest peers picked, and Kubernetes keeping living KEPs has exactly this gap and fills it with milestone dates only. A future reader will ask why we did not do that, so the answer is on the record. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
There is no CI in this repository — .github holds issue templates only — so the rule in Proposal Drift is only as binding as the place a reviewer looks. A pull-request template is the one enforcement mechanism available, and it costs a single file. One substantive checkbox: if this revises a merged proposal, it adds a record or says why none is needed. The escape hatch is deliberate and the template names what legitimately takes it — wording fixes, corrections of fact, and decisions the code already explains — because a checkbox that cannot be declined honestly gets ticked without being read. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The field held a whole paragraph on why the preset-field revision was set aside. "This PR" stops resolving the moment the next revision lands, so it was unmaintainable by construction, and Overview directly below it already said the same thing — a third copy of one rationale with no place of its own. Decision 0001 is that place now, so the field becomes Supersedes plus a link. This was #57, stacked on this branch. Folded here instead: the change is one line in a file this branch already edits, so a separate stacked pull request bought nothing and needed a rebase to survive the move of the records directory. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
c631ab7 to
0fa43d8
Compare
Both links to the #17 review carried review id 4959656115, which is the review on #56 rather than the one on #17. The #17 approval is 4593662101; they landed on the right pull request with a dead anchor. Link fix only, permitted on a merged record by the immutability rule; no prose changed. Assisted-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
Timofei Larkin (lllamnyp)
left a comment
There was a problem hiding this comment.
Approving. Every section of the previous review is addressed, and two things came back better than what was asked for.
The prior-art table is the first: Rust and Python freeze merged proposals, so supersession produces the history for free and no second document class is needed, while Kubernetes keeps living proposals as we do and fills the gap with milestone dates rather than reasoning. That framing wasn't in the review, it's the right way to situate this, and recording the frozen-proposal model as a rejected alternative — labelled as the option the largest peers actually chose, and the live fallback if records don't get written — is more honest than the argument needed to be. Backstage's ADR001 and the GOV.UK Design System's proposal 001 also settle the "is bootstrapping theater" question: introducing the mechanism through the mechanism is how these normally start.
The second is Consequences in the ComputePlane record. Replacing the restatement of the release-name invariant with a link to check-release-name.yaml and release_name_test.yaml is the rule in "What belongs in code instead" being applied to the record that motivated it, which is the strongest evidence the rule works.
I pushed one commit on top: both links to the review on #17 carried review id 4959656115, which is the review on this pull request rather than the one on #17. Corrected to 4593662101. Link fix only, no prose touched.
Two things to fix in a follow-up rather than here.
The #53 bullet in the decision-records proposal is stale. It reads that #53 "reopened that proposal's actuation mechanism … the Cluster /scale subresource exposes no status.selector, and upstream cloudnative-pg#7923 is closed as not planned." #53 was reworked and merged on 2026-08-18 with a different account: cloudnative-pg#7923 is closed not_planned, but as an unnoticed duplicate rather than a decline, and the underlying request was implemented by cloudnative-pg#8996, merged 2026-06-04, which populates Cluster.status.selector and wires labelSelectorPath — shipped in CNPG 1.28.4, 1.29.2 and 1.30.0. The resolution became a version bump rather than a design change, and the "decision needed" framing came out. So #53 no longer illustrates a reopened design, and citing #7923 as declined repeats the reading that #53 itself dropped. The other five precedents in that section are unaffected; this is one bullet.
Worth noting why it matters beyond accuracy: a proposal's Context is prose that ages, which is the argument for records in the first place. This one aged in eight days.
The request for the database-autoscaler record sits on a merged pull request. #53 merged on 2026-08-18, and Rollout items 3 and 4 point readers there for work that is still pending — the record itself, plus extracting that proposal's Why this changed section and spike appendix, both still on main. Participants get the notification, but a merged pull request is not a tracker. An issue would hold it.
Neither blocks merging. Nice work on the restructure — it was a lot to ask of a finished pull request.
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
692a0f9
Timofei Larkin (lllamnyp)
left a comment
There was a problem hiding this comment.
Still approved — the #53 bullet is accurate now.
Checked against the corrected account in the database-horizontal-autoscaling proposal on main: the PoC ran on 1.27.x, cloudnative-pg#8996 populates Cluster.status.selector and wires labelSelectorPath, and it shipped in v1.28.4, v1.29.2 and v1.30.0 without a 1.27 backport. Dropping the #7923 closed as not planned claim is the right call — the label was accurate but the inference wasn't, and #2574/#8996 is the work that actually landed. "Revised that proposal again … the resolution was a version bump rather than a design change" is also the honest framing; it keeps the bullet in the evidence list without overstating what #53 reopened.
The other follow-up still stands and still isn't blocking: Rollout items 3 and 4 point at #53, which merged on 2026-08-18, for work that hasn't happened yet — the record itself, and extracting that proposal's Why this changed section and spike appendix. Worth an issue so it doesn't rely on a notification from a closed thread.
The reasoning for why decision records exist and where they live was going to survive only in the #56 thread — which is precisely the failure the change is written to fix. It reproduced its own diagnosis on itself. So it lands as a design proposal, design-proposals/decision-records, with the drift evidence that was previously only in the pull-request description, and a record under it for the placement decision. The proposal states the change and the rationale; the operative rules stay in design-proposals/README.md, because a merged proposal is a reference and not a binding spec. This is not ceremony, on one condition: the record has a losing alternative. "We will write decision records" has none, and a record for it would be a form to copy. The placement question has four real arguments on the other side, and 0001 states them at full strength — the citable monotonic id, Nygard's tooling, the single directory that lists itself, and cross-proposal decisions having no home — then says what decided each one. It also demonstrates per-proposal numbering immediately: two proposals, two records, both 0001. Recorded a fifth alternative that the review did not raise and neither did the original proposal: freezing merged proposals and superseding them, the Rust RFC and Python PEP model, where the history is automatic because supersession is the only way to change anything. Rejected — it contradicts "a reference, not a binding spec" and makes every implementation finding a new proposal — but it is the model the largest peers picked, and Kubernetes keeping living KEPs has exactly this gap and fills it with milestone dates only. A future reader will ask why we did not do that, so the answer is on the record. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Both links to the #17 review carried review id 4959656115, which is the review on #56 rather than the one on #17. The #17 approval is 4593662101; they landed on the right pull request with a dead anchor. Link fix only, permitted on a merged record by the immutability rule; no prose changed. Assisted-By: Claude Opus 5 <noreply@anthropic.com> Signed-off-by: Timofei Larkin <lllamnyp@gmail.com>
This PR adds
decisions/for architectural decisions we already made, next to design proposals that propose them.Design proposal is intent, written before the work. When implementation contradicts design we revise proposal in place, so it reads as if it always said current thing, and reasoning that changed our minds stays only in PR diff.
It already happens here. #44 and #42 revised DHA to HPA-on-scale-subresource after spike, #53 reopened actuation because cnpg scale subresource has no selector, same shape in #40, #41, #36. Every one is implementation finding rewriting accepted proposal, with why left in PR body. compute-plane proposal even carries a
Revision (this PR):paragraph in its metadata because there was no other place to put that rationale.What is inside:
decisions/README.md- when to write a record and when not, numbering, statuses, immutability rule, review.decisions/template.md- Context / Decision / Why not the alternatives / Consequences / Revisit if.decisions/0001-computeplane-ships-as-an-operator-owned-module.md- backfilled from design-proposal: compute plane for untrusted-code workloads #17 to design-proposal(compute-plane): deliver as kind: Kubernetes presets, not a new kind (per #26 / #17 review) #27 to design-proposal(compute-plane): deliver as an operator-owned extra module wrapping apps/kubernetes (supersedes #27) #33, why ComputePlane ships as operator-owned module and not as preset fields onkind: Kubernetes.Root README gets a row in "Where do I file?", Proposal Drift now says to write a record instead of only editing proposal, and proposal template gets Decisions section.
Two rules carry this. Record is never edited after merge, it gets superseded by a new one. Review checks accuracy and not agreement, so one maintainer is enough, decision was already made and if review turns into re-litigating it then it was not settled and belongs in proposal instead.
Please check
Decidersline on 0001. I put Andrei Kvapil (@kvaps) and myself. Timofei Larkin review on #17 was half of the objection that drove the change but i did not want to guess handle, so he is named in text only.