mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
feat(secrets): add optional descriptions to workspace secrets (#6796)
* feat(secrets): add optional descriptions to workspace secrets Workspace secrets already have a backing credential row with a description column, but nothing surfaced it. Teammates had no way to record what a secret is for. - Add a Description field to the secret detail page, matching the integrations credential page, gated on workspace-secret admin - Fold the value and description editors into one Save/Discard pair and one unsaved-changes guard; two guards cannot coexist, since each seeds its own same-URL history entry - Match descriptions in the secrets settings search - Expose description on GET/PUT /api/v2/secrets and in the CLI Descriptions are workspace-only: env_personal credential rows are per-workspace mirrors of one user-global secret, so one saved there would exist in a single workspace, and a personal secret has no teammates to inform. The API rejects a description on personal scope rather than silently dropping it, and omitting it on PUT leaves any existing description untouched so a value rotation cannot erase it. * fix(secrets): address review findings on secret descriptions - Patch the credential detail cache optimistically on update. `onMutate` cancelled the detail query but only patched the lists, so a detail-backed editor stayed dirty after a successful save until the refetch landed — long enough for Discard to restore the pre-save value over the committed one, and for Back to open the unsaved-changes guard. - Memoize `useSecretValue`'s returned callbacks and object, per the hook convention, so the composed form's save/discard stop churning per render. - Reject a description on a personal secret in the domain layer rather than only at the v2 boundary. The internal credential update path accepted one for any type, writing data every reader hides. - Normalize an empty description to null so the API and UI agree. - Correct the secrets documentation, which described a Display Name field the detail view does not have and omitted the scope rule. - Drop the CLI's copy of the 500-character bound; it can't import the contract, so a copy only drifts from the message the API already returns. - Collapse a redundant save guard and align the description write gate with the render gate. Leaves the integrations credential page byte-identical to staging. * fix(secrets): keep the API docs example and CLI column order stable Backward-compatibility fixes for anyone who never sets a description. - Move the blank-to-null normalization out of the contract and into the route. A Zod `.transform()` on any property drops the whole request schema's OpenAPI examples, which had silently removed the Set Secret request example from the published docs. - Append the CLI `description` column instead of inserting it before `updated`. `--output text` is positional, so inserting would shift every field an existing script cuts. - Reject a description on a personal secret with a message that says so, rather than dropping the field and falling through to the generic "no updatable fields" error.
This commit is contained in:
@@ -1787,6 +1787,7 @@ sim secrets set <name> [options]
|
||||
| --- | --- | --- |
|
||||
| `--scope <scope>` | Yes | Secret ownership scope. Accepted values: `workspace`, `personal`. |
|
||||
| `--value <value>` | No | Secret value; visible to shell history when supplied directly. |
|
||||
| `--description <description>` | No | What the secret is for, shown to teammates; workspace scope only. Omit to leave an existing description unchanged. |
|
||||
|
||||
</CommandTable>
|
||||
|
||||
|
||||
@@ -80,5 +80,6 @@ sim secrets set <name> [options]
|
||||
| --- | --- | --- |
|
||||
| `--scope <scope>` | Yes | Secret ownership scope. Accepted values: `workspace`, `personal`. |
|
||||
| `--value <value>` | No | Secret value; visible to shell history when supplied directly. |
|
||||
| `--description <description>` | No | What the secret is for, shown to teammates; workspace scope only. Omit to leave an existing description unchanged. |
|
||||
|
||||
</CommandTable>
|
||||
|
||||
@@ -95,14 +95,15 @@ Click **Details** on any secret row to open its detail view.
|
||||
|
||||
<Image
|
||||
src="/static/secrets/secret-details.png"
|
||||
alt="Secret details view showing Display Name, Description, and Members sections"
|
||||
alt="Secret details view showing Key, Value, Description, and Members sections"
|
||||
width={700}
|
||||
height={400}
|
||||
/>
|
||||
|
||||
From here you can:
|
||||
|
||||
- Edit the **Display Name** and **Description**
|
||||
- View the **Key** and edit the **Value**
|
||||
- Edit the **Description** — an optional note telling teammates what the secret is for. Workspace secrets only; a personal secret is not shared, so it has none
|
||||
- Manage **Members** — invite teammates by email and assign them an **Admin** or **Member** role
|
||||
|
||||
Click **Save** to apply changes, or **Back** to return to the list.
|
||||
|
||||
@@ -5052,6 +5052,17 @@
|
||||
"enum": ["workspace", "personal"],
|
||||
"description": "Whether the secret belongs to the workspace or to the caller. A personal secret belongs to the caller across every workspace, not to one workspace."
|
||||
},
|
||||
"description": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "What the secret is for, as set on the workspace secret. Always null for a personal secret, which has no shared audience."
|
||||
},
|
||||
"role": {
|
||||
"type": "string",
|
||||
"enum": ["admin", "member"],
|
||||
@@ -5070,7 +5081,7 @@
|
||||
"description": "ISO 8601 timestamp when the secret was last updated."
|
||||
}
|
||||
},
|
||||
"required": ["name", "scope", "role", "createdAt", "updatedAt"],
|
||||
"required": ["name", "scope", "description", "role", "createdAt", "updatedAt"],
|
||||
"additionalProperties": false,
|
||||
"title": "Secret metadata",
|
||||
"description": "Public secret metadata without the stored secret value."
|
||||
@@ -5107,6 +5118,7 @@
|
||||
{
|
||||
"name": "STRIPE_API_KEY",
|
||||
"scope": "workspace",
|
||||
"description": "Production billing key — rotate quarterly.",
|
||||
"role": "admin",
|
||||
"createdAt": "2026-06-01T09:14:00.000Z",
|
||||
"updatedAt": "2026-06-20T14:02:11.000Z"
|
||||
@@ -5133,6 +5145,7 @@
|
||||
"data": {
|
||||
"name": "STRIPE_API_KEY",
|
||||
"scope": "workspace",
|
||||
"description": "Production billing key — rotate quarterly.",
|
||||
"role": "admin",
|
||||
"createdAt": "2026-06-01T09:14:00.000Z",
|
||||
"updatedAt": "2026-06-20T14:02:11.000Z"
|
||||
@@ -5160,6 +5173,18 @@
|
||||
"maxLength": 65536,
|
||||
"description": "Write-only secret value. It is never returned.",
|
||||
"writeOnly": true
|
||||
},
|
||||
"description": {
|
||||
"description": "What the secret is for, shown to teammates. Workspace scope only — sending it for a personal secret is rejected. Omit it to leave an existing description untouched; send null or an empty string to clear one.",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"maxLength": 500
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["workspaceId", "scope", "value"],
|
||||
|
||||
Reference in New Issue
Block a user