Skip to content

docs(decisions): add a decision record log alongside design proposals - #56

Open
myasnikovdaniil wants to merge 2 commits into
mainfrom
docs/decisions-log
Open

docs(decisions): add a decision record log alongside design proposals#56
myasnikovdaniil wants to merge 2 commits into
mainfrom
docs/decisions-log

Conversation

@myasnikovdaniil

@myasnikovdaniil myasnikovdaniil commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

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:

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 Deciders line 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.

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>
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 84052ac4-be87-4632-8364-bad8263a4be2

📥 Commits

Reviewing files that changed from the base of the PR and between 93e347a and efdf056.

📒 Files selected for processing (7)
  • README.md
  • decisions/0001-computeplane-ships-as-an-operator-owned-module.md
  • decisions/README.md
  • decisions/template.md
  • design-proposals/README.md
  • design-proposals/compute-plane/README.md
  • design-proposals/template.md

📝 Walkthrough

Walkthrough

The change adds decision-record documentation, an ADR template, an accepted ComputePlane architecture decision, and guidance for linking decisions to design proposals.

Changes

Decision records and proposal guidance

Layer / File(s) Summary
Decision record workflow
README.md, decisions/README.md, decisions/template.md
Documents decision-record purpose, authoring rules, lifecycle, review expectations, and the ADR template.
ComputePlane architecture decision
decisions/0001-computeplane-ships-as-an-operator-owned-module.md, design-proposals/compute-plane/README.md
Records the operator-owned ComputePlane module decision and links it from the ComputePlane proposal.
Proposal decision guidance
design-proposals/README.md, design-proposals/template.md
Requires significant implementation divergences to link decision records and adds a Decisions section to proposal guidance and templates.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a decision record log alongside design proposals.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/decisions-log

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@lllamnyp Timofei Larkin (lllamnyp) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 Decisions section added to design-proposals/template.md becomes 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 proposaldesign-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 in design-proposals/README.md and the template; the proposal states the change and the rationale, not the law.
  • A decision record under itdesign-proposals/decision-records/decisions/0001-decision-records-live-with-their-proposals.md — recording the placement decision and, in the Why not the alternatives section, 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-18 date comes from. A record whose job is to be the durable pointer to the reasoning points everywhere except at it. Add a Decided 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 placement should 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.
  • Deciders should 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 yet is 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/computeplane and the PackageSource convention. The durable argument is structural and survives #39 deleting the extra category: capabilities and schemas are per-kind, so two postures over one chart need two ApplicationDefinitions; ApplicationDefinitionSpec has 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 narrow openAPISchema doesn'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: true the 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 none from the Proposal: field. Per section 1, make it required.
  • Add Decided in: for the PR or issue thread where the decision was argued, and one line under Why not the alternatives asking 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 by and Implemented in must 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 computeplane release-name invariant is enforced at packages/extra/computeplane/templates/check-release-name.yaml and pinned by tests/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.md with one checkbox: this PR revises a merged proposal; it adds a record under that proposal's decisions/, 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's Why this changed section 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 for Revision (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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants