Skip to content

feat(estate): name the identity the operating loop runs as, on the loop's own box (#357) - #385

Merged
lex00 merged 1 commit into
mainfrom
feat/rbac-badge-357
Sep 8, 2026
Merged

feat(estate): name the identity the operating loop runs as, on the loop's own box (#357)#385
lex00 merged 1 commit into
mainfrom
feat/rbac-badge-357

Conversation

@lex00

@lex00 lex00 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Closes #357. Takes pinhole 0.3.8 (INTENTIUS/pinhole#122), published for this.

What it answers

The estate already drew the operating loop and #324 marked its namespace with a glyph. The glyph says a loop lives here. This says who it acts as — "who is allowed to do this", readable off the picture instead of by opening a card.

The identity is Kubernetes RBAC, not IAM: the ServiceAccount the tick's pods run under. OperatorStack gives each hosted ConvergeOp its own, which is why the home-level claim has three answers rather than one:

badge
every tick shares an account runs as staging-converge-sa
ticks run as N accounts (the ordinary least-privilege shape) runs as 2 identities
no tick named one nothing

That last row matters. Below detail 3 the IR carries no pod spec, so "no account named" means not read, not default. Reporting default there would be a claim about the cluster that behold has not earned. Absence stays silent, which is what the issue asked for.

Read where the cluster reads it

spec.jobTemplate.spec.template.spec.serviceAccountName on the CronJob, not a label match to a ServiceAccount card. A label match agrees with the truth by construction and disagrees silently the moment someone overrides defs.cronJob.

The channel did not exist

GroupBox.mark (pinhole#119) is a glyph from a fixed vocabulary. An identity is a name, and the only way to get text onto a box was its title — which #119 itself established is load-bearing, because behold re-parses namespace <ns> to re-parent helm releases. So pinhole 0.3.8 adds GroupBox.badge?: string, purely additive, and behold takes it: renderArchitecture gains groupBadges, keyed by container id exactly as groupMarks is, threaded through all four architecture call sites.

Verified rendered

one tick             badges={"namespace chant-operator":"runs as staging-converge-sa"}   in svg: "runs as staging-converge-sa"
two identities       badges={"namespace chant-operator":"runs as 2 identities"}          in svg: "runs as 2 identities"

Tests

just check green: 88 files, 1725 tests. The operator fixture gains the pod spec OperatorStack really emits; new cases cover the one/many/unknown claim, the badge wording, the box keyed by its structural id, and silence for a namespace with no loop, a loop with no box, and a loop below the spec tier. render.test.ts covers the plumbing, a mark and a badge coexisting, and byte-identical output with no badges.

🤖 Generated with Claude Code

https://claude.ai/code/session_0124XP1LJTZxgJRy7Dh1UfXt

…op's own box (#357)

The estate graph already showed the operating loop — an OperatorStack is a
Namespace and a CronJob per ConvergeOp, so the loop draws as ordinary cards,
and #324 marked its namespace box with a glyph. What the picture never said is
the thing an operator actually asks: WHO it acts as.

That identity is Kubernetes RBAC — the ServiceAccount the tick's pods run
under, one per hosted ConvergeOp by OperatorStack's own least-privilege shape.
It is read off the CronJob's pod spec, where the cluster reads it, rather than
matched by label to a ServiceAccount card: a label match would agree with the
truth by construction and disagree silently the moment a caller overrides
`defs.cronJob`.

`operatorIdentity` makes the home-level claim, and the three cases are
genuinely different rather than one with holes:

  one      every tick runs as the same account, and the box names it
  many     the ticks run as N accounts — the ordinary shape — and the box
           says how many; the names are on the tick cards
  unknown  no tick named one. Below detail 3 the IR carries no pod spec at
           all, so this is "not read", and it is reported as nothing rather
           than as `default`.

Absence stays silent, which is what #357 asked for: a namespace with no loop
is untouched, and a loop the projection never boxed gets no badge rather than
an invented box.

The badge needed a channel that did not exist. pinhole's `GroupBox.mark`
(#119) is a glyph from a fixed vocabulary, and an identity is a name; the only
way to put text on a box was its title, which #119 itself established is
load-bearing because behold re-parses it. pinhole 0.3.8 adds
`GroupBox.badge?: string` for exactly this (pinhole#122), and behold takes it:
`renderArchitecture` gains `groupBadges`, keyed by container id like
`groupMarks`, threaded through all four architecture call sites.

Verified rendered: a one-tick loop's box reads "runs as staging-converge-sa"
and a two-tick loop's reads "runs as 2 identities".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0124XP1LJTZxgJRy7Dh1UfXt
@lex00
lex00 merged commit 0f0efbb into main Sep 8, 2026
1 check passed
@lex00
lex00 deleted the feat/rbac-badge-357 branch September 8, 2026 22:40
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.

estate: RBAC member badges — name the identity the operating loop runs as on its member box

1 participant