Skip to content

docs(#1528): document the MySQL DB identity model + edgeuser retirement (D10) - #660

Merged
divyasinghds merged 1 commit into
developfrom
docs/1528-security-db-identity-model
Aug 11, 2026
Merged

docs(#1528): document the MySQL DB identity model + edgeuser retirement (D10)#660
divyasinghds merged 1 commit into
developfrom
docs/1528-security-db-identity-model

Conversation

@divyasinghds

@divyasinghdsdivyasinghds commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

What

Document the in-cluster MySQL database identity model in docs/SECURITY.md — the S0/S1 groundwork for backend#1528 (RFC-0003 D10 close-out: retire the root-equivalent edgeuser, mint tb_meta + tb_ingest). The doc described the training-pod credential model (G1) but never the DB identity model, and mentioned edgeuser only obliquely.

Changes (docs only)

  • §4.1.1 MySQL database identity model — the two databases (metadata, training_test_datasets), the least-privilege identities (per-experiment users, tb_credmgr, tb_meta, tb_ingest) and edgeuser's legacy root-equivalence, as a per-identity scope / consumer / status table, plus the staged S1 → S2 → S3 retirement.
  • §8.10 residual riskedgeuser root-equivalence as a tracked gap with the staged, per-fleet, reversible-until-S3 rollout (mirrors the §8.2 egress-lockdown style).
  • §10 — code-path rows for the minting (client-runtime:sql_utils.ensure_*_account) and the serviceDbAccounts gate.
  • §11 — document-history entry.

Accuracy / current state

Reflects the fleet reality today, not the end state:

  • edgeuser is still root-equivalent and still the default authentication identity for jobs-manager, requests-proxy, and ingestion pods.
  • S1 is merged and minting-capable but serviceDbAccounts defaults off, so tb_meta/tb_ingest aren't minted anywhere yet and nothing consumes them until S2.
  • tb_credmgr + per-experiment users are the already-shipped perExperimentDbCreds mechanism (backend#1181).

Review ask

This is customer-facing security wording — please sanity-check the identity/scope/consumer table in §4.1.1 and the §8.10 rollout against your intent for the D10 close-out.

Refs: backend#1528 (D10 close-out); client-runtime#290 / client#643 (S1).

🤖 Generated with Claude Code


Note

Low Risk
Documentation-only changes to customer-facing security wording; no runtime or credential behavior changes.

Overview
docs/SECURITY.md now documents the in-cluster MySQL least-privilege identity model and the planned retirement of root-equivalent edgeuser, as part of RFC-0003 D10 / backend#1528.

Under §4.1.1, it adds a table for metadata vs training_test_datasets, identities (per-experiment users, tb_credmgr, tb_meta, tb_ingest, legacy edgeuser), who uses each, and current status—including that serviceDbAccounts (default off) mints tb_meta/tb_ingest at jobs-manager startup without consumers yet. It also describes the S1 → S2 → S3 rollout (mint → switch consumers → drop edgeuser).

§8.10 tracks edgeuser root-equivalence as an in-progress G1 residual risk with the same staged, fleet-by-fleet rollout. §10 links minting to client-runtime:sql_utils.ensure_*_account and the serviceDbAccounts gate in chart templates. §11 adds a 2026-08 history entry.

Reviewed by Cursor Bugbot for commit 5254ac1. Bugbot is set up for automated code reviews on this repo. Configure here.

@divyasinghdsdivyasinghds self-assigned this Aug 11, 2026
@divyasinghds
divyasinghds requested review from aptracebloc and removed request for saadqbalAugust 11, 2026 07:36
LukasWodka added a commit that referenced this pull request Aug 11, 2026
… PR trigger (backend#1681) (#665)
`Source-of-truth drift` is a REQUIRED status check on develop and on main, but
`drift-checks.yaml` only fires on PRs touching scripts/, client/ or itself. A PR
outside those paths therefore never produces the check, GitHub leaves it at
"Expected - waiting for status to be reported", and the PR is unmergeable with no
failing check to point at.
Measured on 2026-08-11 this was blocking three open PRs at once - client#651
(org-standards sync), #657 (Makefile) and #660 (docs) - none of which touch the
filtered paths. Two of the three had already been approved.
`pull_request` loses its paths filter so the required check always reports. The
job is ~10s (measured across its last three runs), which is nothing next to a
permanently stuck PR. `push` keeps its filter: pushes are not gated by required
checks, so path-scoping there is free and correct.
The repo already documents this hazard in standard-checks.yml's header; this is
the same rule applied to the workflow that broke it.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…nt (D10)
SECURITY.md described the training-pod credential model (G1) but never the
MySQL database identity model, and mentioned edgeuser only obliquely. The
RFC-0003 D10 close-out (backend#1528) makes that model a first-class security
property, so document it:
- §4.1.1 — the two databases, the least-privilege identities (per-experiment
users, tb_credmgr, tb_meta, tb_ingest) and edgeuser's legacy
root-equivalence, with per-identity scope/consumer/status, and the staged
S1→S2→S3 retirement.
- §8.10 — edgeuser root-equivalence as a tracked residual risk with the
staged, per-fleet rollout (mirrors the §8.2 egress-lockdown style).
- §10 — code paths for the minting (sql_utils.ensure_*_account) and the
serviceDbAccounts gate.
- §11 — history entry.
Docs only; reflects current state accurately (S1 merged/minting-capable but
default-off, consumers still on edgeuser until S2). Part of the backend#1528
S0/S1 groundwork ("document the identity model — it never mentions edgeuser
today").
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@divyasinghds
divyasinghdsforce-pushed the docs/1528-security-db-identity-model branch from b8991e5 to 5254ac1CompareAugust 11, 2026 09:57
@divyasinghds
divyasinghds merged commit 43dcbd2 into developAug 11, 2026
13 checks passed
@divyasinghds
divyasinghds deleted the docs/1528-security-db-identity-model branch August 11, 2026 10:00
LukasWodka added a commit that referenced this pull request Aug 14, 2026
… (backend#1729) (#715)
The guard added in #706 parses all four CLIENT_ENV declarations and compares
them. It ran in helm-ci.yaml's `Helm lint` job. `Helm lint` is not a required
status check on this repo -- the required contexts on develop are `Unit tests`,
`Lint`, `quality/*`, `version-bump-gate/version-check` and `Source-of-truth
drift` -- so a PR that made the four declarations disagree was mergeable with
the guard red. It could advise; it could not block.
That is the shape backend#1729 is about, in the epic's own flagship deliverable:
a mechanism that appears to verify something without being connected to the
outcome it claims to control.
`Source-of-truth drift` is the correct home and needed no new configuration:
- it is REQUIRED on develop and on main, so a disagreement blocks the merge;
- it deliberately carries NO `paths:` on pull_request (its header records why
-- a path-filtered required check bricks PRs outside those paths, which is
what happened to #651/#657/#660 on 2026-08-11), so the guard now also runs
on the installer-only PRs helm-ci's filter would have skipped;
- it is already the home of the other duplicated-declaration guard
(check-drift.sh), and the guard needs only bash + python3, ~2s.
helm-ci loses the step and the two `paths:` entries that arrived with it in
#706. `scripts/install-k8s.ps1` genuinely leaves -- helm-ci is the repo's
heaviest workflow (a real k3d cluster plus two 4-platform matrices) and a
PowerShell installer edit has no other reason to start it. The explicit
`scripts/lib/common.sh` entry also goes, but that one was always redundant:
the pre-existing `scripts/lib/**` glob still matches it, so helm-ci's
triggering on a common.sh change is unchanged.
Mutation-proved rather than assumed: with `staging)` renamed in
scripts/lib/common.sh the guard exits 1; restored, it exits 0. The mutation
anchor was asserted to have applied first -- an inert mutation and good
coverage look identical in a log.
Verified green on this branch before arming: all four declarations agree and
all six spellings are exercised (dev 12, development 2, prod 11, production 4,
staging 9, stg 6).
Refs backend#1729
LukasWodka added a commit that referenced this pull request Aug 27, 2026
* ci(2651): split the chart test tier so it can be a required check
The 585-test helm-unittest tier (34 suites, ~4s) lived in helm-ci.yaml and was
required on no branch. All of it could be red and the PR still merged, which
made every assertion in it advisory — including the ones #2606 had just
repaired. backend#1729 rule 2: a guard in a non-required CI job is advice.
A NEW WORKFLOW RATHER THAN A PATHS EDIT, and the reason matters because the
ticket's scope assumed the edit.
`helm-ci.yaml` could not simply lose its `pull_request` paths filter. It is the
repo's heaviest workflow, and two jobs carry NO job-level `if:` — `upgrade-e2e`
(Fleet auto-upgrade E2E, k3d, timeout 30m) and `seal-check-e2e` (Seal-check
egress-enforcement, k3d, timeout 30m). They are gated ONLY by that filter, so
dropping it would have started ~60 minutes of k3d E2E on every PR in the repo,
docs-only ones included. Moving the cheap tier out costs nothing and leaves the
expensive jobs byte-identical: helm-ci.yaml keeps its filter and now holds
lint / template / ingestor-multiarch / the three e2e jobs.
`helm-unit.yaml` therefore has NO `paths:` on `pull_request` — a path-filtered
required check never creates its check run on a PR outside those paths, so
GitHub leaves it at "Expected - waiting for status to be reported" and the PR is
unmergeable forever. drift-checks.yaml's header records that blocking
client#651, #657 and #660 on 2026-08-11. `push` keeps a filter: pushes are not
gated by required checks, so scoping there is free. Same split drift-checks uses.
scripts/tests/helm-unittest-gated.sh keeps it that way, and runs inside the
REQUIRED `Source-of-truth drift` job — so the guard protecting the gate is
itself gated. It refuses a `pull_request` paths filter, pins the job name (the
name IS the status-check context, so a near-miss un-gates the tier exactly as
silently as the paths trap), refuses a second job producing the same context,
and refuses a gate that no longer runs `helm unittest ./client` — a required
check that executes nothing reports success forever.
WHAT THE GUARD DOES NOT DO, said in its header rather than implied: it does not
read the live branch-protection list, so it cannot prove the context is
currently required. That copy lives in GitHub's config, not this repo, and the
drift job has no token for it. The header carries the `gh api` one-liner to
check by hand. An earlier draft of helm-unit.yaml claimed the guard "asserts the
name against the live protection list" — false, and precisely the rule-7 defect
this ticket family is about, so it is corrected in the same change.
ARMED WHILE GREEN (rule 4): `helm unittest ./client` on develop @ 76f4690 is
34 suites / 585 tests / 4.1s, all passing. Note 585, not the 581 the ticket
measured — it has grown, which is the argument for gating it.
Mutations on the guard, each asserting its anchor applied:
G1 THE TRAP: paths filter on pull_request -> FAIL
G2 rename the job to a near-miss -> FAIL
G3 gate stops running the suite -> FAIL
G4 a second workflow produces the context -> FAIL
G5 workflow deleted -> FAIL
G6 workflow unparseable -> FAIL
G7 push loses its paths filter -> FAIL
G8 NEGATIVE CONTROL, reword a comment -> PASS
Makefile: the guard is registered in DRIFT_GUARDS, the repo's only declaration
of that set. Two claims there are now false and fixed in the same change — that
"`Helm unit tests` is required on neither" (it is the whole point of this PR),
and that `helm-unittest` maps to "helm-ci.yaml `unittest`" (it moved).
drift 24/24 green · helm-lint, helm-vocab, helm-template, check-style, shellcheck
all clean.
PROTECTION IS NOT CHANGED BY THIS PR. Adding the context to client/develop and
client/main is an admin action on a shared repo, so it needs an explicit
decision — the command and the measurements are in the PR body.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* ci(2651): the paths guard could not see paths-ignore (backend#2651)
The guard exists to stop a required check falling into the permanent-pending
trap, and it looked for `paths:` only. `paths-ignore:` is its sibling and skips
the workflow the same way -- a PR outside either filter never gets a check run,
so the required context sits at "Expected - waiting for status to be reported"
forever. A later `paths-ignore:` would have reopened exactly the trap this
guard exists to close, while the guard stayed green. Bugbot, #872.
The same blind spot was in the `push` arm with the opposite sign: it warned
"push lost its paths: filter" for a workflow filtered with `paths-ignore:` --
a false positive in the one place this guard is meant to be trusted.
Both keys are now named once, in PATH_FILTER_KEYS, so the check and its own
error message cannot disagree about which keys were examined. Two entries
rather than a `paths*` prefix rule: the set is closed and short, and a prefix
would also catch a future key that does something else.
Mutation-proved against the real workflow, each anchor asserted before the
run:
pull_request + paths-ignore: -> exit 1, message names `paths-ignore:`
pull_request + paths: -> exit 1, message names `paths:` (unchanged)
push paths -> paths-ignore -> exit 0, no false "lost its filter"
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for freeto 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.

4 participants

@divyasinghds@saadqbal@aptracebloc@LukasWodka