Repository navigation
Migration story for stored cleartext datasource credentials: operator-initiated re-homing into sys_secret (blocked by the managed-schemaMode refusal) #8155
Description
Split out of #8081 scope item 3 by the domain:services PM seat. The maintainer's Option-A ruling on #7990 put this in scope of the programme — "The existing-rows migration is in scope of the programme, not optional" — and #8081's dispatch order asked for the story, not its execution. This card carries the proposal the dev produced. ⛔ Not graded by me; no domain:* label.
Blocked-by: #8153
An inventory already exists, and it is free
/meta re-parses every stored row and badges it _diagnostics: {valid:false, errors:[…]} carrying the full refusal message. Operators can already enumerate the affected rows — the migration needs no discovery phase, which removes the usual first half of a migration card.
/meta read path) must not strip or break _diagnostics, or this inventory disappears.
Recommended shape — (b) admin tooling, per-datasource, operator-initiated
A Setup action — "Move credential to the secret store" — that reads the stored cleartext, binds it through the existing SecretBinder.bind(), writes external.credentialsRef, and removes the inline key only after the ref is durably written.
Is it destructive? No, and deliberately so. No credential value is discarded — it is re-homed from cleartext into sys_secret. The inline key is deleted only once the encrypted row exists. It is per-datasource and operator-initiated, so there is no sweep over a table. That matters: the ruling's "escalate if destructive" clause is what this shape is designed to stay clear of.
Rejected, with reasons
- (c) one-time boot script — decides the secret's identity with no operator present and mutates rows at boot. That is precisely the destructive-sweep shape sys_secret: rotations that happened BEFORE #8030's fix left decryptable orphans on deployed instances, and nothing reaps them #8103 split out of [security] Rotating an encrypted setting silently does nothing — the new ciphertext is written but
sys_setting.value_encstill points at the old handle, so a leaked credential stays live after a 200 #8030 for the same reason. The ruling's escalate-if-destructive clause points straight at it. - (a) read-time refusal + re-entry prompt — non-destructive, but it breaks working datasources in order to prompt a human, and it is unnecessary once (b) exists. Worth keeping only as a fallback for rows (b) cannot bind.
⚠️ Sequencing — this is blocked, and running it early makes things worse
The migration's entire output is external.credentialsRef on an existing row. Per #8153, writing that onto a row whose schemaMode defaults to 'managed' is exactly the shape that fails re-parse.
⇒ Running this before #8153 is settled would convert every affected managed datasource from "invalid because it holds cleartext" to "invalid because it holds a credentialsRef" — a migration that moves rows from one invalid state to another while appearing to succeed. #8153 first.
Constraints
- ⛔ No sweep over the credential table without an operator. If the shape drifts back toward (c), it needs escalation per the ruling, not a lane decision.
- The ref must be durably written before the inline key is removed — a crash between the two must leave the row working, not credential-less.
⚠️ Re-running must be idempotent and must not mint a secondsys_secretrow for an already-migrated datasource (the orphan-accumulation failure mode measured in sys_secret: rotations that happened BEFORE #8030's fix left decryptable orphans on deployed instances, and nothing reaps them #8103 and guarded in PR fix(plugin-webhooks): move webhook custom headers onto the encrypted channel (#7986) #8114'sheadersPatch).- Rows (b) cannot bind need a stated outcome — that is where fallback (a) earns its place.
Provenance: #8081 dev report (comment 5269923121, §③), #7990 maintainer ruling (comment 5266068845), my ACCEPT receipt (#8081 comment 5270102954). Related: #8103 (the same destructive-sweep boundary, for sys_secret orphans), #8154, #8152.
Activity
hotlong commentedon Aug 12, 2026
Triage: lands in the services lane (Setup action over the existing
SecretBinder.bind()path inservice-datasource) ⇒domain:services, type Feature,pm:blockedon theBlocked-by: #8153line already in the body. The maintainer's #7990 Option-A ruling already puts the migration in scope, and the recommended shape (b) — operator-initiated, per-datasource, ref durably written before the inline key is removed — is designed to stay clear of the escalate-if-destructive clause, so no new decision is needed here; what gates it is purely the #8153 contract question (now escalated asneeds-user-decision), because running the migration first would move rows from one invalid state to another.For the unlock re-check when #8153 resolves: re-verify that the
_diagnostics.valid:falseinventory still exists (the #8154 redaction work is under an explicit constraint not to break it) and that the idempotency/no-second-secret-row constraint from #8103's measurements is in the dispatch order verbatim.Generated by Claude Code
hotlong commentedon Aug 14, 2026
Unlock scan + grade: upstream
Blocked-by: #8153cleared — #8153 closed (PR #8588 merged 2026-08-14T00:53Z:external.credentialsRef, and only it, now schema-legal onschemaMode 'managed'), so the shape this migration writes is no longer self-invalidating. Rather than requeueing, escalated toneeds-user-decision: a stored-data migration shape is on the human floor regardless of prism agreement. The half that IS settled: the 2026-08-13 Option-A ruling on #7990 put the existing-rows migration in scope of the programme, not optional — what needs the maintainer is the shape below.Proposed shape (the card's recommendation, carried unchanged): operator-initiated, per-datasource Setup action "Move credential to the secret store" — read stored cleartext, bind via the existing
SecretBinder.bind(), writeexternal.credentialsRef, delete the inline key only after the ref is durably written. Non-destructive by construction; no discovery phase needed (/meta_diagnosticsalready enumerates affected rows).Four-prism block:
sys_metadata.metadatais a general cleartext sink: any authored artefact whose schema permits an inline credential lands it there (datasourceconfig.password, connectorauthentication) #7990 programme's stored-rows half with mechanisms the platform already has (SecretBinder,sys_secret); no new concept, no schema change beyond what Every wizard-created datasource with a password is badged invalid today — the service writes external.credentialsRef onto rows whose schemaMode defaults to 'managed', which the schema refuses #8153 landed.getDatasource().config, fix the false "credential-stripped" claim, and write the stored-cleartext-rows migration story #8081/fix(service-datasource): stop serving stored credentials on the datasource read path, and fix the false "credential-stripped" claim (#8081) #8126 fixes holds cleartext credentials at rest today; the ruling itself declares this in scope.Recommendation: approve shape (b) as carried (all four prisms point the same way; it stays in the inbox only because stored-data migration shapes are a named human-floor category). One ordering constraint to confirm alongside: #8154 (metadata read-path redaction, unblocked this round) must keep
_diagnosticsintact — recorded on both cards.Premise re-check lines:
git log --oneline -3 -- packages/spec/src/data/datasource.zod.ts· state of #8154 ·git grep -n "credentialsRef" origin/main -- packages/services/service-datasource/src | head.本评论来自分诊座位 Routine。
Generated by Claude Code
hotlong commentedon Aug 14, 2026
Maintainer ruling (2026-08-14, verbatim: 「同意你的建议」, approving item 1 of the eight-item decision-box list: 「#8155:批 shape (b),target:v17 进席 A(耦合注记 #8154)?」): shape (b) approved as carried — operator-initiated per-datasource Setup action, bind via existing
SecretBinder.bind(), writeexternal.credentialsRef, delete the inline key only after the ref is durably written; shape (c) stays the fallback for rows (b) cannot bind.needs-user-decision→pm:queue,target:v17; enters the v17 bug-focus Seat A (#8667). Coupling constraint for the implementer: #8154 (metadata read-path redaction, same seat) must NOT strip_diagnostics— that badge is this card's discovery mechanism.Generated by Claude Code
feat(service-datasource): operator-initiated credential re-homing int…
test(spec): the last data/ file group's test titles state each cited …