Skip to content

feat(quota): meter notes on platform-scoped subjects against their author - #793

Closed
kevwilliams wants to merge 1 commit into
fix/notes-quota-platform-scoped-subjectsfrom
feat/notes-user-quota
Closed

kevwilliams wants to merge 1 commit into
fix/notes-quota-platform-scoped-subjectsfrom
feat/notes-user-quota

Conversation

@kevwilliams

Copy link
Copy Markdown
Contributor

Follow-up to #792 (stacked on it — review that one first).

Summary

#792 stopped notes on contacts, users and organizations from claiming project quota, which unblocked them but left them unmetered. This meters them against the note's author instead, so staff notes are counted and limits become possible later.

  • New notes-per-user registration, consumer iam.miloapis.com/User
  • Claim policies for platform-scoped subjects, resolving the consumer from spec.creatorRef
  • The project and user policies carry complementary constraints and partition note subjects between them

creatorRef is set by the mutating webhook from the authenticated user, so it can't be spoofed into someone else's quota, and the quota plugin validates after mutating webhooks run, so it's always populated by the time the claim template renders.

Two things worth knowing

It needs a new resource type, not a second registration on notes.miloapis.com/notes. The registration cache is keyed by resource type alone with no duplicate guard, so two registrations on one type would silently clobber each other.

⚠️ Blocked on datum. Bucket limits come only from active grants and start at 0, so merging this before a GrantCreationPolicy for notes.miloapis.com/user-notes exists in datum would deny contact notes again — re-breaking exactly what #792 fixed. The chainsaw tests supply the grant directly, so CI here passes either way; production would not. Please don't merge until the datum policy is in place.

The datum policy should omit parentContext — notes on contacts are created in the core control plane where Users also live, so the grant is written locally, and that's the branch that sets an owner reference, so a user's grant is garbage-collected with the user.

Test plan

  • Unit test asserts the two policies partition subject groups — exactly one fires for any group, checked against the shipped YAML so a future subject kind can't fall through both or match both
  • Unit test renders the user policy's claim template and asserts the consumer resolves to the note's author and the request targets user-notes
  • kustomize build config/services renders all three new objects
  • task test:unit passes
  • New test/quota/user-notes-quota chainsaw test — not run locally, no cluster. Asserts a contact note creates a claim consuming the author's quota, that it's granted, and that no project-scoped claim is created.
  • test/crm/note-contact-lifecycle gains a grant fixture, since its notes now consume author quota. Also unverified locally.

…thor

Notes on contacts, users and organizations belong to no project, so #792
stopped them claiming project quota. That left them unmetered entirely.

Meter them against the note's author instead. spec.creatorRef is set by the
mutating webhook from the authenticated user, so it can't be spoofed into
another user's quota, and the quota plugin validates after mutating webhooks
run, so it is always populated by the time the claim template renders.

Adds a notes-per-user registration on a new resource type. It cannot reuse
notes.miloapis.com/notes: the registration cache is keyed by resource type
alone, so two registrations on one type would silently clobber each other.

The project and user claim policies carry complementary constraints and
partition note subjects between them; a unit test asserts that invariant
against the shipped config so a future subject kind can't fall through
either policy or match both.

Requires a matching GrantCreationPolicy in the datum repo. Without a grant
the bucket limit is 0 and notes are denied, so that must land first. The
chainsaw tests supply the grant directly.
kevwilliams added a commit to datum-cloud/datum that referenced this pull request Sep 17, 2026
…ts (#291)

## Summary

Grants each user an allowance for notes on platform-scoped subjects —
contacts, users and organizations. Those notes belong to no project, so
milo meters them against the note's author instead of a project.

Pairs with milo-os/milo#793, and **this one has to land first**: bucket
limits come only from active grants and start at 0, so shipping the milo
side without this grant would deny contact notes outright.

## Why no parentContext

The existing project policy sets `parentContext` because a note on a
project-scoped subject is created in that project's control plane, so
its claim lands there and the grant has to follow.

These notes are different — they're created in the core control plane,
where Users also live, so the grant belongs there and the policy writes
locally. That's also the branch that sets an owner reference on the
trigger, so a user's grant is garbage-collected with the user. The
project-scoped policies don't get that.

## The one thing needing a decision

**The 1000 default is a starting point, not a researched number.** It's
meant to be generous enough not to bite real usage while making the
notes countable — the point of this work is that they stop being
invisible, not that the limit binds. Project notes are 100, but that's
per project; this is per person across every contact they touch, so the
same number would be much tighter in practice.

Happy to change it to whatever product thinks is right.

## Test plan

- [x] `kustomize build config/services/notes.miloapis.com` renders both
grant policies
- [x] All top-level kustomizations still build
- [ ] Not exercised against a cluster. The milo-side chainsaw tests
supply an equivalent grant directly, so the mechanism is covered there;
this PR is the production wiring of it.
@kevwilliams

Copy link
Copy Markdown
Contributor Author

We decided this is not needed right now in #792

kevwilliams added a commit to datum-cloud/datum that referenced this pull request Sep 19, 2026
Reverts #291.

## Why

#291 granted every user an allowance for
`notes.miloapis.com/user-notes`, to meter notes on contacts against
their author. The milo side that defined and claimed that resource type
(milo-os/milo#793) has been closed — notes on contacts are only ever
created by platform users, so there's nothing to meter.

That leaves this policy minting a `ResourceGrant` per user for a
resource type that no longer exists anywhere, growing with every new
user.

## Blast radius

None. Nothing claims `user-notes` now that milo-os/milo#793 is closed,
and a grant for a resource type with no claims is inert. The
project-scoped notes grant (100 per project) is untouched and still
doing real work.

Contact notes keep working — milo-os/milo#792 is what fixed those, and
it does so by not creating a claim at all, so it needs no grant.

## Test plan

- [x] `kustomize build config/services/notes.miloapis.com` renders only
the project grant policy
- [x] All top-level kustomizations still build
- [x] No remaining references to `user-notes` in config
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.

1 participant