Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .changeset/sys-secret-tri-producer-field-help.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
---
"@objectstack/platform-objects": patch
---

fix(platform-objects): `sys_secret` field help stops asserting the settings-only reading (#12550)

`sys_secret` has **three** privileged producers (#4270) — the object's own
`managedBy` note has said so for a while — but its most load-bearing field
descriptions still described a settings-only table. Those strings are not
internal comments: they are the field help an operator reads in the
`sys_secret` grid, and they compile into the shipped translation bundles.

Measured on `origin/main@f93df4db`, by producer symbol rather than by line:

| producer | `namespace` | `key` | the reference lives at |
|---|---|---|---|
| `SettingsService` (`settings-service.ts`, `secretStore.insert`) | settings namespace | specifier key | `sys_setting.value_enc` |
| engine `encryptSecretFields` (`objectql/src/engine.ts`) | **object name** | **field name** | a `secret:<id>` ref on the business row itself |
| datasource credential binder (`datasource-secret-binder.ts`) | **caller-supplied**, default `datasource` | datasource name | the artefact's `sys_secret:<id>` credentialsRef |

So `'Settings namespace this secret belongs to.'` / `'Specifier key within the
namespace.'` / `'Opaque handle referenced by sys_setting.value_enc.'` were each
true of one producer out of three, and the pair they describe was presented as
if it identified an owner. That is exactly the `(namespace, key)` attribution
reading #8103's re-measurement rejected — the reason
`sys-secret-orphan-report.ts` reports a row it cannot attribute as
`'unattributable'` rather than `'orphaned'`. Field help asserting the rejected
reading is the safety-relevant direction of this drift.

Corrected here: the object description, and the `namespace` / `key` / `id`
field descriptions, now name the producer-scoped reality and point at
`managedBy`. The `en` bundle was regenerated with the repo's own
`pnpm i18n:extract`; the three translated locales carried translations of the
superseded English, so their four affected leaves were re-translated by hand —
the action `<locale>.objects.generated.ts`'s own header prescribes when a
source string changes — and the bundles plus their `--source-hashes`
companions then come from one extract run.

Text only. No field is added, removed, renamed or re-typed; no validation,
persistence or access rule moves; every `sys_secret` payload that parsed before
parses identically. ⛔ A producer/owner column stays out of scope — that is a
persist-path change and belongs to whoever takes that decision.
Original file line numberDiff line numberDiff line change
Expand Up@@ -3012,11 +3012,11 @@ export const enObjects: NonNullable<TranslationData['objects']> = {
sys_secret: {
label: "Secret",
pluralLabel: "Secrets",
description: "Cipher store referenced by sys_setting handles. Never holds plaintext.",
description: "Cipher store written by three privileged producers (see managedBy); each holds its handle in its own column. Never holds plaintext.",
fields: {
id: {
label: "ID",
help: "Opaque handle referenced by `sys_setting.value_enc`."
help: "Opaque handle. The reference lives in the writing producer's own holder column — `sys_setting.value_enc`, a `secret:` ref on a business row, or a datasource `credentialsRef` — so a row unreferenced by `sys_setting` is not thereby unreferenced."
},
created_at: {
label: "Created At",
Expand All@@ -3028,11 +3028,11 @@ export const enObjects: NonNullable<TranslationData['objects']> = {
},
namespace: {
label: "Namespace",
help: "Settings namespace this secret belongs to."
help: "Producer-scoped label, not a settings namespace in general: `SettingsService` writes the settings namespace, the engine's `secret`-field encryption writes the object name, the datasource binder writes its caller-supplied scope (default `datasource`). See managedBy."
},
key: {
label: "Key",
help: "Specifier key within the namespace."
help: "Producer-scoped label paired with `namespace`: the settings specifier key, the encrypted `secret` field's name, or the datasource name. The pair records how the producer addressed the value; it does not identify which producer wrote the row."
},
kms_key_id: {
label: "KMS Key ID",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -3012,11 +3012,11 @@ export const esESObjects: NonNullable<TranslationData['objects']> = {
sys_secret: {
label: "Secreto",
pluralLabel: "Secretos",
description: "Almacén cifrado al que hacen referencia los identificadores de sys_setting. Nunca contiene texto sin formato.",
description: "Almacén cifrado escrito por tres productores privilegiados (véase managedBy); cada uno guarda su identificador en su propia columna. Nunca contiene texto sin formato.",
fields: {
id: {
label: "ID",
help: "Identificador opaco al que hace referencia `sys_setting.value_enc`."
help: "Identificador opaco. La referencia vive en la columna propia del productor que lo escribió — `sys_setting.value_enc`, una referencia `secret:` en una fila de negocio o el `credentialsRef` de un origen de datos —, de modo que una fila no referenciada por `sys_setting` no está por ello sin referencias."
},
created_at: {
label: "Creado el",
Expand All@@ -3028,11 +3028,11 @@ export const esESObjects: NonNullable<TranslationData['objects']> = {
},
namespace: {
label: "Espacio de nombres",
help: "Espacio de nombres de ajustes al que pertenece este secreto."
help: "Etiqueta acotada al productor, no un espacio de nombres de ajustes en general: `SettingsService` escribe el espacio de nombres de ajustes, el cifrado de campos `secret` del motor escribe el nombre del objeto y el vinculador de orígenes de datos escribe el ámbito indicado por quien lo llama (por defecto `datasource`). Véase managedBy."
},
key: {
label: "Clave",
help: "Clave del especificador dentro del espacio de nombres."
help: "Etiqueta acotada al productor y emparejada con `namespace`: la clave del especificador de ajustes, el nombre del campo `secret` cifrado o el nombre del origen de datos. El par registra cómo el productor direccionó el valor; no identifica qué productor escribió la fila."
},
kms_key_id: {
label: "ID de clave KMS",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -3012,11 +3012,11 @@ export const jaJPObjects: NonNullable<TranslationData['objects']> = {
sys_secret: {
label: "シークレット",
pluralLabel: "シークレット",
description: "sys_setting ハンドルが参照する暗号ストア。平文は保持しません。",
description: "3 つの特権的な書き込み経路が書き込む暗号ストア(managedBy を参照)。ハンドルはそれぞれの経路が自身の列で保持します。平文は保持しません。",
fields: {
id: {
label: "ID",
help: "`sys_setting.value_enc` が参照する不透明なハンドル。"
help: "不透明なハンドル。参照は書き込んだ経路それぞれの保持列にあります — `sys_setting.value_enc`、業務レコード上の `secret:` 参照、またはデータソースの `credentialsRef`。したがって `sys_setting` から参照されていない行が、参照されていない行であるとは限りません。"
},
created_at: {
label: "作成日時",
Expand All@@ -3028,11 +3028,11 @@ export const jaJPObjects: NonNullable<TranslationData['objects']> = {
},
namespace: {
label: "名前空間",
help: "このシークレットが属する設定名前空間。"
help: "書き込み経路ごとに意味が決まるラベルであり、一般に設定名前空間とは限りません:`SettingsService` は設定名前空間を、エンジンの `secret` フィールド暗号化はオブジェクト名を、データソースバインダーは呼び出し側が指定したスコープ(既定は `datasource`)を書き込みます。managedBy を参照。"
},
key: {
label: "キー",
help: "名前空間内の識別キー。"
help: "`namespace` と対になる、書き込み経路ごとに意味が決まるラベル:設定の識別キー、暗号化される `secret` フィールド名、またはデータソース名。この対は書き込み経路が値をどう指定したかを記録するもので、どの経路がその行を書き込んだかを示すものではありません。"
},
kms_key_id: {
label: "KMS キー ID",
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -3012,11 +3012,11 @@ export const zhCNObjects: NonNullable<TranslationData['objects']> = {
sys_secret: {
label: "密钥",
pluralLabel: "密钥",
description: "由 sys_setting 句柄引用的密文存储。绝不保存明文。",
description: "由三个特权写入方写入的密文存储(见 managedBy);每个写入方各自在自己的列中保存句柄。绝不保存明文。",
fields: {
id: {
label: "ID",
help: "`sys_setting.value_enc` 引用的不透明句柄。"
help: "不透明句柄。引用保存在写入方各自的持有列中 —— `sys_setting.value_enc`、业务记录上的 `secret:` 引用,或数据源的 `credentialsRef`;因此某行未被 `sys_setting` 引用,并不等于它未被引用。"
},
created_at: {
label: "创建时间",
Expand All@@ -3028,11 +3028,11 @@ export const zhCNObjects: NonNullable<TranslationData['objects']> = {
},
namespace: {
label: "命名空间",
help: "该密钥所属的设置命名空间。"
help: "按写入方限定的标签,并非普遍意义上的设置命名空间:`SettingsService` 写入设置命名空间,引擎的 `secret` 字段加密写入对象名,数据源绑定器写入调用方提供的范围(默认 `datasource`)。见 managedBy。"
},
key: {
label: "键",
help: "命名空间内的标识键。"
help: "与 `namespace` 配对的、按写入方限定的标签:设置的标识键、被加密的 `secret` 字段名,或数据源名。该配对记录的是写入方如何寻址该值;它并不标明是哪个写入方写入了该行。"
},
kms_key_id: {
label: "KMS 密钥 ID",
Expand Down
37 changes: 29 additions & 8 deletions packages/platform-objects/src/system/sys-secret.object.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,7 +50,8 @@ export const SysSecret = ObjectSchema.create({
// SettingsService / the datasource secret-binder read with no principal
// (middleware falls open for principal-less internal calls).
access: { default: 'private' },
description: 'Cipher store referenced by sys_setting handles. Never holds plaintext.',
description:
'Cipher store written by three privileged producers (see managedBy); each holds its handle in its own column. Never holds plaintext.',
highlightFields: ['namespace', 'key', 'kms_key_id', 'version', 'rotated_at'],
listViews: {
all: {
Expand All@@ -65,7 +66,8 @@ export const SysSecret = ObjectSchema.create({
id: Field.text({
label: 'ID',
readonly: true,
description: 'Opaque handle referenced by `sys_setting.value_enc`.',
description:
'Opaque handle. The reference lives in the writing producer\'s own holder column — `sys_setting.value_enc`, a `secret:` ref on a business row, or a datasource `credentialsRef` — so a row unreferenced by `sys_setting` is not thereby unreferenced.',
}),

created_at: Field.datetime({
Expand All@@ -81,23 +83,42 @@ export const SysSecret = ObjectSchema.create({
}),

/**
* Namespace/key duplicated from `sys_setting` for forensic
* convenience — lets operators answer "which secret backs
* mail.api_key right now?" without joining the K/V table.
* The authoritative link is `sys_setting.value_enc → sys_secret.id`.
* `(namespace, key)` is a PRODUCER-SCOPED pair, not an owner. Each of the
* three producers named in `managedBy` (#4270) fills it from its own
* vocabulary, and the reference to the resulting row lives in a different
* column for each:
*
* - `SettingsService` — settings namespace + specifier key; referenced by
* `sys_setting.value_enc`.
* - the engine's `secret`-field encryption (`encryptSecretFields`) —
* object name + field name; referenced by a `secret:<id>` ref stored on
* the business row itself.
* - the datasource credential binder — caller-supplied namespace (default
* `datasource`) + datasource name; referenced by the artefact's
* `sys_secret:<id>` credentialsRef.
*
* ⛔ So the pair does NOT attribute a row to a producer, and a lookup that
* finds no `sys_setting` behind a pair has found nothing: #8103's
* re-measurement rejected exactly that reading, which is why
* `service-settings/src/sys-secret-orphan-report.ts` reports a row it
* cannot attribute as `'unattributable'` rather than `'orphaned'`. The pair
* is for inspecting and rotating a known secret, never for deciding what a
* row belongs to.
*/
namespace: Field.text({
label: 'Namespace',
required: true,
maxLength: 128,
description: 'Settings namespace this secret belongs to.',
description:
'Producer-scoped label, not a settings namespace in general: `SettingsService` writes the settings namespace, the engine\'s `secret`-field encryption writes the object name, the datasource binder writes its caller-supplied scope (default `datasource`). See managedBy.',
}),

key: Field.text({
label: 'Key',
required: true,
maxLength: 128,
description: 'Specifier key within the namespace.',
description:
'Producer-scoped label paired with `namespace`: the settings specifier key, the encrypted `secret` field\'s name, or the datasource name. The pair records how the producer addressed the value; it does not identify which producer wrote the row.',
}),

/** Identifier of the KMS key used to wrap `ciphertext`. */
Expand Down
Loading