From adb117b1c430b6cc0e20e8c6b6893cf8a1e727b0 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 15:48:56 +0000 Subject: [PATCH] fix(platform-objects): sys_secret field help stops asserting the settings-only reading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The object's own managedBy note records three privileged producers, but the object description and the namespace / key / id field descriptions still described a settings-only table — and those strings are the field help an operator reads in the grid, compiled into the shipped translation bundles. They asserted exactly the (namespace, key) attribution reading #8103's re-measurement rejected. Re-verified by producer symbol on origin/main@f93df4db: SettingsService -> settings namespace + specifier key; ref at sys_setting.value_enc engine encryptSecretFields -> object name + field name; ref is a secret: on the business row datasource binder -> caller-supplied scope (default 'datasource') + datasource name; ref is the artefact's sys_secret: credentialsRef Bundles regenerated with the repo's own `pnpm i18n:extract` (11 files, incl. the --source-hashes companions). The three translated locales carried translations of the superseded English, so their four affected leaves were re-translated by hand — the action .objects.generated.ts's own header prescribes when a source string changes. Text only: no field added, removed, renamed or re-typed; no validation, persistence or access rule moves. A producer/owner column stays out of scope. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01W6HFzyH98W1YaQXhJUJt6o --- .../sys-secret-tri-producer-field-help.md | 42 +++++++++++++++++++ .../apps/translations/en.objects.generated.ts | 8 ++-- .../translations/es-ES.objects.generated.ts | 8 ++-- .../translations/ja-JP.objects.generated.ts | 8 ++-- .../translations/zh-CN.objects.generated.ts | 8 ++-- .../src/system/sys-secret.object.ts | 37 ++++++++++++---- 6 files changed, 87 insertions(+), 24 deletions(-) create mode 100644 .changeset/sys-secret-tri-producer-field-help.md diff --git a/.changeset/sys-secret-tri-producer-field-help.md b/.changeset/sys-secret-tri-producer-field-help.md new file mode 100644 index 0000000000..a90e3f4b6b --- /dev/null +++ b/.changeset/sys-secret-tri-producer-field-help.md @@ -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:` ref on the business row itself | +| datasource credential binder (`datasource-secret-binder.ts`) | **caller-supplied**, default `datasource` | datasource name | the artefact's `sys_secret:` 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 `.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. diff --git a/packages/platform-objects/src/apps/translations/en.objects.generated.ts b/packages/platform-objects/src/apps/translations/en.objects.generated.ts index 143aa862a8..44ba384370 100644 --- a/packages/platform-objects/src/apps/translations/en.objects.generated.ts +++ b/packages/platform-objects/src/apps/translations/en.objects.generated.ts @@ -3012,11 +3012,11 @@ export const enObjects: NonNullable = { 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", @@ -3028,11 +3028,11 @@ export const enObjects: NonNullable = { }, 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", diff --git a/packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts b/packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts index 4c1505edf9..39f7fb45b9 100644 --- a/packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts +++ b/packages/platform-objects/src/apps/translations/es-ES.objects.generated.ts @@ -3012,11 +3012,11 @@ export const esESObjects: NonNullable = { 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", @@ -3028,11 +3028,11 @@ export const esESObjects: NonNullable = { }, 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", diff --git a/packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts b/packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts index ff3eaad560..2c2771ce8f 100644 --- a/packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts +++ b/packages/platform-objects/src/apps/translations/ja-JP.objects.generated.ts @@ -3012,11 +3012,11 @@ export const jaJPObjects: NonNullable = { 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: "作成日時", @@ -3028,11 +3028,11 @@ export const jaJPObjects: NonNullable = { }, namespace: { label: "名前空間", - help: "このシークレットが属する設定名前空間。" + help: "書き込み経路ごとに意味が決まるラベルであり、一般に設定名前空間とは限りません:`SettingsService` は設定名前空間を、エンジンの `secret` フィールド暗号化はオブジェクト名を、データソースバインダーは呼び出し側が指定したスコープ(既定は `datasource`)を書き込みます。managedBy を参照。" }, key: { label: "キー", - help: "名前空間内の識別キー。" + help: "`namespace` と対になる、書き込み経路ごとに意味が決まるラベル:設定の識別キー、暗号化される `secret` フィールド名、またはデータソース名。この対は書き込み経路が値をどう指定したかを記録するもので、どの経路がその行を書き込んだかを示すものではありません。" }, kms_key_id: { label: "KMS キー ID", diff --git a/packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts b/packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts index 9915346e11..4d8893d96d 100644 --- a/packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts +++ b/packages/platform-objects/src/apps/translations/zh-CN.objects.generated.ts @@ -3012,11 +3012,11 @@ export const zhCNObjects: NonNullable = { 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: "创建时间", @@ -3028,11 +3028,11 @@ export const zhCNObjects: NonNullable = { }, namespace: { label: "命名空间", - help: "该密钥所属的设置命名空间。" + help: "按写入方限定的标签,并非普遍意义上的设置命名空间:`SettingsService` 写入设置命名空间,引擎的 `secret` 字段加密写入对象名,数据源绑定器写入调用方提供的范围(默认 `datasource`)。见 managedBy。" }, key: { label: "键", - help: "命名空间内的标识键。" + help: "与 `namespace` 配对的、按写入方限定的标签:设置的标识键、被加密的 `secret` 字段名,或数据源名。该配对记录的是写入方如何寻址该值;它并不标明是哪个写入方写入了该行。" }, kms_key_id: { label: "KMS 密钥 ID", diff --git a/packages/platform-objects/src/system/sys-secret.object.ts b/packages/platform-objects/src/system/sys-secret.object.ts index 9f61348f5d..363fad8859 100644 --- a/packages/platform-objects/src/system/sys-secret.object.ts +++ b/packages/platform-objects/src/system/sys-secret.object.ts @@ -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: { @@ -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({ @@ -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:` 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:` 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`. */