From bbe0b17799c98f2f47dbe37266bb75d5d5e05037 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 10:33:31 +0000 Subject: [PATCH 1/2] fix(audit): declare sys_activity.type as an open, author-extensible vocabulary (#11507) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The select's declared options are the platform BUILT-IN set with documented open-vocabulary semantics — not a closed enum the runtime never enforces. Maintainer ruling 2026-08-24, direction 4. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01APWX2AwT3a4xDcjPCe8bk4 --- .../sys-activity-type-open-vocabulary.md | 43 +++++ ...tivity-type-vocabulary-enforcement.test.ts | 78 ++++++--- .../sys-activity-type-open-vocabulary.test.ts | 156 ++++++++++++++++++ .../sys-activity-type-vocabulary.test.ts | 24 +++ .../src/objects/sys-activity.object.ts | 51 ++++++ .../src/translations/en.objects.generated.ts | 1 + .../translations/es-ES.objects.generated.ts | 1 + .../translations/ja-JP.objects.generated.ts | 1 + .../translations/zh-CN.objects.generated.ts | 1 + 9 files changed, 334 insertions(+), 22 deletions(-) create mode 100644 .changeset/sys-activity-type-open-vocabulary.md create mode 100644 packages/plugins/plugin-audit/src/objects/sys-activity-type-open-vocabulary.test.ts diff --git a/.changeset/sys-activity-type-open-vocabulary.md b/.changeset/sys-activity-type-open-vocabulary.md new file mode 100644 index 0000000000..6983ad2434 --- /dev/null +++ b/.changeset/sys-activity-type-open-vocabulary.md @@ -0,0 +1,43 @@ +--- +'@objectstack/plugin-audit': patch +--- + +Say out loud that `sys_activity.type` is an open, author-extensible vocabulary +— the declared options are the platform's **built-in** set, not a closed enum + +An author reading the declaration learned "writing another value will be +rejected". That was false in three independent ways, and the declaration was +the only place that did not say so. + +1. Every field on `sys_activity` is `readonly: true`, and `validateRecord` + skips readonly fields on both write branches, so the `invalid_option` check + a `select` normally implies **never runs** on this column. +2. ADR-0052 §5b.2 `activityMilestones[].type` is `z.string().optional()` in + the spec and is forwarded verbatim by the audit writer + (`if (milestone.type) activityType = milestone.type`) — a shipped, + documented, author-facing channel straight into the column. +3. An app's own server-side action writes the column directly + (`ctx.api.object('sys_activity').insert({ type: … })`); no grep of this + repository can see those sites. + +Maintainer ruling, 2026-08-24 (#11507, direction 4 of four): the column **is** +an open vocabulary, ADR-0052 §5b.2 **stays** a sanctioned write path, and +every closed map over this vocabulary is now the bug. The status quo was the +one option more dangerous than either end state — most of all to an AI writing +metadata, which reads the declaration and believes it. + +So the declaration now carries the semantics, in the field's own +`description` — the slot the spec declares for exactly this and, unlike a +source comment, one the contract carries wherever the metadata goes (the +metadata API, the i18n bundles, whatever an author or an AI reads about this +field). No new schema concept was invented: `FieldSchema` has no +open/closed-vocabulary key, and the pin measures that rather than asserting +it, so the day `packages/spec` grows one this declaration is told to move. + +Nothing about enforcement changed — that was direction 3 and it was **not** +ruled. `validateRecord` is untouched, the built-in set is unchanged (twelve +values), and both existing vocabulary tests keep every assertion they had. +What changed in them is what a red MEANS: the two cases that used to be filed +as "a defect, characterized — delete these when enforcement lands" now measure +a ruled contract, and say that rejecting an author-contributed value is a +contract change to re-open #11507 over, not a fix to adapt them to. diff --git a/packages/plugins/plugin-audit/src/activity-type-vocabulary-enforcement.test.ts b/packages/plugins/plugin-audit/src/activity-type-vocabulary-enforcement.test.ts index 466ca948e3..2a18c6cc3c 100644 --- a/packages/plugins/plugin-audit/src/activity-type-vocabulary-enforcement.test.ts +++ b/packages/plugins/plugin-audit/src/activity-type-vocabulary-enforcement.test.ts @@ -39,6 +39,17 @@ * keeps writing, the row keeps landing, every assertion below stays green) and * red THERE. Breaking a writer is red here and green there. Neither file alone * covers this object. + * + * ## 2026-08-24 — what the ruling on #11507 changed about this file + * + * Nothing about the measurements; everything about what they MEAN. #8203 wrote + * §3 as "a defect, characterized", with the instruction to delete those cases + * once enforcement landed. The maintainer ruled (direction 4) that this column + * is an OPEN, author-extensible vocabulary: the declared options are the + * platform's BUILT-IN set, ADR-0052 §5b.2 stays a sanctioned write path, and an + * author-contributed value landing verbatim is the contract. So §3 is no longer + * a characterized defect — it is the only end-to-end measurement of the ruled + * behavior, and it stays. See the §3 header for what a red there now means. */ import { describe, it, expect } from 'vitest'; @@ -233,8 +244,11 @@ describe('[#8203] sys_activity.type — the writers emit declared values', () => expect( DECLARED_TYPES, `audit-writers.ts wrote sys_activity.type '${t}', which the object does not ` - + 'declare. Nothing rejects it — the field is readonly, so `validateRecord` ' - + 'skips it — so the row lands and the contract denies it (#8203).', + + 'declare as a built-in. Nothing rejects it — the field is readonly, so ' + + '`validateRecord` skips it — so the row lands unannounced. The vocabulary is ' + + 'open to AUTHORS (#11507); the platform writing outside its own built-in set ' + + 'is still a finding, because that set is what the platform promises to write, ' + + 'label and offer as a filter. Declare the value, or stop writing it (#8203).', ).toContain(t); } }); @@ -297,19 +311,34 @@ describe('[#8203] CONTROL — a writable select rejects the undeclared value', ( }); // --------------------------------------------------------------------------- -// 3. The finding — the identical write is ACCEPTED when the field is readonly +// 3. The ruled contract — an author-contributed value is stored verbatim // --------------------------------------------------------------------------- -describe('[#8203] the declared vocabulary is unenforceable while the field is readonly', () => { +describe('[#8203/#11507] an author-contributed type is accepted — the open-vocabulary contract', () => { /** - * ⚠️ These two cases assert a DEFECT, characterized. They are the card's - * observation made mechanical, and they are written to go red the day it is - * fixed — which is the correct signal, not a false alarm. + * ⚠️ These two cases used to be labelled "a DEFECT, characterized", with the + * instruction: go red when enforcement lands, then delete them. That + * instruction is RETIRED, and deleting them now would delete the only + * end-to-end measurement of a ruled contract. + * + * Maintainer ruling, 2026-08-24, #11507 (direction 4): `sys_activity.type` is + * an OPEN, author-extensible vocabulary. The declared options are the + * platform's built-in set; ADR-0052 §5b.2 `activityMilestones[].type` stays a + * sanctioned write path; an author-contributed value landing verbatim is what + * the platform means, not a hole in it. Directions 2 and 3 were considered and + * NOT ruled. + * + * So a red here no longer reads "the fix landed". It reads: something has + * started REJECTING an author-contributed value — which is direction 3, a + * shipped authoring surface turned into a rejection path. Do not adapt these + * cases to it and do not weaken them; re-open #11507, because that is a + * maintainer call and not a test-fixing exercise. * - * If one fails with "expected 'not_a_declared_type' … received a rejection", - * enforcement has landed (the engine-wide direction #8203 names, in - * `record-validator.ts`). That is the fix: delete these two cases, keep §1 - * and the census file, and close #8203. + * The mechanism is unchanged and still worth knowing: every field on this + * object is `readonly`, and `validateRecord` skips readonly fields on both + * branches, so the option check never runs. §2 is the control proving the + * validator runs at all — which is what makes the acceptance below a + * measurement rather than a test that forgot to assert. */ it('a direct write of an undeclared type into sys_activity is accepted verbatim', async () => { const { engine, storeFor } = await boot(); @@ -321,18 +350,22 @@ describe('[#8203] the declared vocabulary is unenforceable while the field is re expect(DECLARED_TYPES).not.toContain(UNDECLARED); expect( activityTypes(storeFor), - 'sys_activity.type no longer accepts an undeclared option. If this is because ' - + 'readonly-field option enforcement landed, that is the fix #8203 describes — ' - + 'retire this case and its neighbour and close the card.', + 'sys_activity.type no longer accepts an undeclared option. Per the 2026-08-24 ' + + 'ruling on #11507 this column is an OPEN vocabulary: a value outside the ' + + 'built-in set is legitimate and is stored verbatim, so a rejection here is a ' + + 'CONTRACT CHANGE (direction 3, considered and not ruled), not a fix. Re-open ' + + '#11507 instead of adapting this case.', ).toEqual([UNDECLARED]); }); /** - * The same hole reached through a REAL, shipped authoring surface rather than + * The same path reached through a REAL, shipped authoring surface rather than * a hand-made insert: `activityMilestones[].type` is `z.string().optional()` - * in the spec, so any metadata author can name any string, and it lands in a - * column whose enum denies it. This is the authoring-time version of the - * defect and the one an AI-written metadata app would hit first. + * in the spec, so any metadata author can name any string and it lands. This + * is the authoring-time face of the open vocabulary, and the one an AI-written + * metadata app meets first — which is exactly why the declaration now says so + * in its own `description` (#11507), instead of showing that author a list + * that reads closed. */ it('a milestone declaring an undeclared type writes it — the authoring-surface hole', async () => { const { engine, storeFor } = await boot(); @@ -342,10 +375,11 @@ describe('[#8203] the declared vocabulary is unenforceable while the field is re expect(DECLARED_TYPES).not.toContain('escalated_to_legal'); expect( activityTypes(storeFor), - 'a milestone-declared `type` outside the sys_activity.type enum no longer reaches ' - + 'the row. If option enforcement (or a spec-level constraint on ' - + '`activityMilestones[].type`) landed, that is the fix #8203 describes — retire ' - + 'this case and close the card.', + 'a milestone-declared `type` outside the built-in sys_activity.type set no longer ' + + 'reaches the row. ADR-0052 §5b.2 is a SANCTIONED author write path and the ' + + '2026-08-24 ruling on #11507 kept it one, so option enforcement here — or a ' + + 'spec-level constraint on `activityMilestones[].type` — breaks shipped author ' + + 'metadata by design. Re-open #11507 before changing this.', ).toEqual(['created', 'escalated_to_legal']); }); }); diff --git a/packages/plugins/plugin-audit/src/objects/sys-activity-type-open-vocabulary.test.ts b/packages/plugins/plugin-audit/src/objects/sys-activity-type-open-vocabulary.test.ts new file mode 100644 index 0000000000..5f1abcb5c4 --- /dev/null +++ b/packages/plugins/plugin-audit/src/objects/sys-activity-type-open-vocabulary.test.ts @@ -0,0 +1,156 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import { describe, it, expect } from 'vitest'; +import { ObjectSchema } from '@objectstack/spec/data'; +import { SysActivity } from './index.js'; + +/** + * #11507 — the declaration of `sys_activity.type` must say what the column + * actually is: an OPEN, author-extensible vocabulary whose declared options are + * the platform's BUILT-IN set. + * + * ## The ruling this file executes + * + * Maintainer, 2026-08-24, on #11507 (direction 4 of the four the card framed), + * verbatim: 「四维分析一致的,接手你的建议。」 Recorded on the card as: + * + * > the column is an open, author-extensible vocabulary. […] make the + * > declaration honest (the select's declared options become the built-in set + * > with documented open-vocabulary semantics — not a closed enum the runtime + * > never enforces); ADR-0052 §5b.2 stays a sanctioned write path […] + * > Downstream: every closed map over this vocabulary is now the bug. + * + * Directions 2 (rule the producer non-conformant) and 3 (enforce the vocabulary) + * were NOT ruled. Nothing here should be read as a step toward either. + * + * ## Why the declaration was dishonest, in one paragraph + * + * Three things were true at once. The field is a `select` over a fixed list — + * which normally means "anything else is `invalid_option`". Every field on this + * object is `readonly: true` and `validateRecord` skips readonly fields on both + * write branches, so that check never runs. And ADR-0052 §5b.2's + * `activityMilestones[].type` (`z.string().optional()` in `object.zod.ts`) is a + * shipped, documented, author-facing channel that forwards ANY string into the + * column — `audit-writers.ts`: `if (milestone.type) activityType = milestone.type`. + * An author (a human, and far more often an AI writing metadata) who reads the + * declaration builds the model "writing another value will be rejected", and + * that model is false. The status quo was more dangerous than either end state, + * which is what the four-facet analysis said and what the ruling adopted. + * + * ## The mechanism, and why this one + * + * `FieldSchema` has no key that means "open vocabulary" — no `openVocabulary`, + * no `restricted`, no `allowCustomValues` (measured below, third case, so the + * next author does not have to guess). Adding one is a `packages/spec` change + * and therefore a different seat's card, not something to invent here. The slot + * the spec DOES declare for exactly this is the field's own `description` + * ("Tooltip/Help text", `field.zod.ts` — the documentation slot, distinct from + * `placeholder` and `inlineHelpText`), and it is carried BY THE CONTRACT: the + * exported `SysActivity` is the output of `ObjectSchema.create()`, i.e. of a + * real parse, so what this file reads is metadata that ships — to the metadata + * API, to the i18n bundles, to whatever an author or an AI reads about this + * field — and not a source comment that stops at the file boundary. + * + * So: the source docblock carries the reasoning, and the `description` carries + * the contract. This file pins the second, because only the second travels. + */ + +/** The `type` field as it is actually declared (post-parse). */ +function typeField(): { type?: string; description?: unknown; options?: unknown } { + return ((SysActivity as { fields?: Record> }) + .fields?.type ?? {}) as { type?: string; description?: unknown; options?: unknown }; +} + +/** Option values declared by the `type` select field. */ +function typeValues(): string[] { + const options = (typeField().options ?? []) as Array; + return options.map((o) => (typeof o === 'string' ? o : String(o.value))); +} + +describe('[#11507] sys_activity.type is an OPEN vocabulary and the declaration says so', () => { + /** + * The half of the ruling that is easy to lose: "open" does NOT mean + * "undeclared". The declared options are the BUILT-IN set — the values the + * platform itself writes and the values a picker/filter offers — and they + * stay declared. A future author who reads "open vocabulary" and deletes the + * option list would take the built-in set, the labels, the i18n leaves and + * the census pin with it. + */ + it('keeps a declared built-in set — an open vocabulary is not an absent one', () => { + const field = typeField(); + expect( + field.type, + 'sys_activity.type stopped being a `select`. The #11507 ruling made the vocabulary ' + + 'OPEN, not undeclared: the declared options are the platform built-in set and ' + + 'they stay. Widening the column to a bare `text` deletes the built-in set, its ' + + 'labels and its i18n leaves, and leaves authors nothing to extend FROM.', + ).toBe('select'); + expect( + typeValues().length, + 'sys_activity.type declares no options. See above: open ≠ undeclared (#11507).', + ).toBeGreaterThan(0); + }); + + /** + * The load-bearing assertion, and the deliverable of #11507. The three + * markers are the three things an author must be able to learn FROM THE + * DECLARATION ITSELF: + * - the declared list is the BUILT-IN set (not the whole legal set); + * - the vocabulary is OPEN (an author may contribute a value); + * - the sanctioned way to do that is ADR-0052 §5b.2, which stays a write + * path per the ruling — not a rejection path. + * + * Asserted as markers rather than as an exact string: the wording is meant to + * be improvable, the three facts are not. + */ + it('declares open-vocabulary semantics in the CONTRACT, not only in a source comment', () => { + const description = typeField().description; + const hint = + 'sys_activity.type carries no open-vocabulary documentation in its declaration. ' + + 'Per the 2026-08-24 maintainer ruling on #11507 this column is an OPEN, ' + + 'author-extensible vocabulary: the declared options are the BUILT-IN set, an ' + + 'author-contributed value (ADR-0052 §5b.2 `activityMilestones[].type`, or an ' + + "app action's own `insert`) is legitimate, and it is stored verbatim — nothing " + + 'rejects it, because every field here is `readonly` and `validateRecord` skips ' + + 'readonly fields. A bare option list without that sentence tells an author — ' + + 'most often an AI writing metadata — that another value would be REJECTED, ' + + 'which is false. Put it back in `description` (the contract carries it; a ' + + 'source comment does not).'; + + expect(typeof description, hint).toBe('string'); + const text = String(description); + expect(text.length, hint).toBeGreaterThan(0); + for (const marker of [/built-in/i, /open vocabulary/i, /ADR-0052/]) { + expect(marker.test(text), `${hint}\nMissing from the description: ${marker}`).toBe(true); + } + }); + + /** + * WHY the mechanism above is prose in `description` rather than a declared + * flag: there is no flag. Measured, not assumed — and written so it goes RED + * the day the spec grows one, which is the day this declaration should move + * the semantics into it (and the day the objectui-side consumer can read the + * openness mechanically instead of being told). + * + * Note what this does NOT claim: that such a key should not exist. Declaring + * one is a `packages/spec` decision and belongs to the spec seat. + */ + it('has no declared spec key for open/closed vocabulary — `description` is the available slot', () => { + const probes = ['openVocabulary', 'restricted', 'allowCustomValues', 'extensible']; + for (const key of probes) { + const candidate = JSON.parse(JSON.stringify(SysActivity)) as { + fields: Record>; + }; + candidate.fields.type[key] = true; + const parsed = ObjectSchema.safeParse(candidate); + expect( + parsed.success, + `FieldSchema now accepts \`${key}\` on a field. If \`packages/spec\` grew a real ` + + 'open/closed-vocabulary declaration, this file is the pin that says so: move ' + + "sys_activity.type's open-vocabulary semantics onto that key (keeping the " + + 'description as help text), and tell the objectui consumer card — a machine-' + + 'readable flag is what lets a renderer stop guessing (#11507).', + ).toBe(false); + } + }); +}); diff --git a/packages/plugins/plugin-audit/src/objects/sys-activity-type-vocabulary.test.ts b/packages/plugins/plugin-audit/src/objects/sys-activity-type-vocabulary.test.ts index 3570674de6..fd215cdb47 100644 --- a/packages/plugins/plugin-audit/src/objects/sys-activity-type-vocabulary.test.ts +++ b/packages/plugins/plugin-audit/src/objects/sys-activity-type-vocabulary.test.ts @@ -23,6 +23,30 @@ import { SysActivity } from './index.js'; * shows both halves, against a control that proves the measurement can fail. * This file is the DECLARATIVE half: the writer census, written as literals. * + * ## 2026-08-24 — the #11507 ruling, and what this census now inventories + * + * The paragraph above described the state of the code; the maintainer then + * ruled what it MEANS (direction 4): `sys_activity.type` is an OPEN, + * author-extensible vocabulary, the declared options are the platform's + * BUILT-IN set, and ADR-0052 §5b.2 stays a sanctioned author write path. The + * declaration now says so in its own `description` + * (`sys-activity.object.ts`), pinned by `./sys-activity-type-open-vocabulary.test.ts`. + * + * Two consequences for THIS file, and no others — every assertion below is + * unchanged: + * + * - The census inventories the BUILT-IN set. A value an app contributes + * through the milestone door or its own action does not belong here just + * because it exists; it belongs to the app that writes it. Declaring one + * (as #11424 did for `scheduled`) is a deliberate choice to take it into the + * platform's set — the platform then owns its label, its i18n leaves and its + * filter — never an obligation created by the mere fact that someone wrote + * it. + * - "An undeclared value is written silently" is no longer a defect to be + * fixed by enforcement. It is the ruled contract. What this file still + * guards is the OTHER direction: that the built-in set stays honest about + * the platform's own writers. + * * The two halves are not redundant, and the asymmetry is the reason both * exist. Narrowing the enum away from a live writer changes NO behavior — the * writer keeps writing, the row keeps landing, every behavioral assertion stays diff --git a/packages/plugins/plugin-audit/src/objects/sys-activity.object.ts b/packages/plugins/plugin-audit/src/objects/sys-activity.object.ts index acd3c75fbe..f5986c56af 100644 --- a/packages/plugins/plugin-audit/src/objects/sys-activity.object.ts +++ b/packages/plugins/plugin-audit/src/objects/sys-activity.object.ts @@ -54,6 +54,50 @@ export const SysActivity = ObjectSchema.create({ group: 'Event', }), + /** + * The activity kind — an OPEN, author-extensible vocabulary whose declared + * options are the platform's BUILT-IN set. Maintainer ruling 2026-08-24 on + * #11507 (direction 4 of the four that card framed), which the `description` + * below carries into the contract; this comment carries the reasoning. + * + * ## Why the declaration used to lie + * + * A `select` over a fixed list normally means "anything else is + * `invalid_option`". Here it never could: + * + * 1. Every field on this object is `readonly: true`, and `validateRecord` + * skips readonly fields on BOTH write branches + * (`objectql/src/validation/record-validator.ts`), so the option check + * never runs. An undeclared value is stored silently. + * 2. ADR-0052 §5b.2 `activityMilestones[].type` is `z.string().optional()` + * in `object.zod.ts` and is forwarded verbatim by `audit-writers.ts` + * (`if (milestone.type) activityType = milestone.type`) — a shipped, + * documented, author-facing channel into this column. + * 3. An app's own server-side action reaches the column directly + * (`ctx.api.object('sys_activity').insert({ type: … })`). No grep of + * THIS repository can see those; both measured sites live in + * objectstack-ai/hotcrm (#11424, read at 5eee1bd). + * + * So an author — most often an AI writing metadata — who read the option + * list learned "another value will be rejected", which was false in three + * independent ways. The ruling closes that by moving the declaration, not + * the runtime: the options are the built-in set, author-contributed values + * are legitimate, and ADR-0052 §5b.2 STAYS a write path (turning it into a + * rejection path was direction 3, which was NOT ruled). + * + * ## What that binds + * + * - Do not "fix" this by enforcing the enum on system-owned writes, and do + * not narrow `activityMilestones[].type`. Either is direction 3; re-open + * #11507 first. + * - Keep the built-in set declared and censused — open is not undeclared. + * The writer census lives in `sys-activity-type-vocabulary.test.ts`, and + * it inventories BUILT-IN values only; an author's value belongs to the + * app that writes it, not to this list. + * - Downstream, every CLOSED map over this vocabulary is now the bug: a + * consumer must render an unknown value, not drop the row. (objectui's + * feed-kind map is the known one; its pin is a card in that lane.) + */ type: Field.select( [ 'created', @@ -71,6 +115,13 @@ export const SysActivity = ObjectSchema.create({ ], { label: 'Type', + description: + 'Activity kind. The declared options are the platform BUILT-IN set of an open ' + + 'vocabulary, not a closed enum: metadata authors may contribute their own values ' + + '(sanctioned channel: `activityMilestones[].type`, ADR-0052 §5b.2), and an ' + + 'undeclared value is stored verbatim rather than rejected. Consumers must render ' + + 'an unknown value instead of assuming this list is exhaustive (maintainer ruling ' + + '2026-08-24, #11507).', required: true, readonly: true, searchable: true, diff --git a/packages/plugins/plugin-audit/src/translations/en.objects.generated.ts b/packages/plugins/plugin-audit/src/translations/en.objects.generated.ts index 66b58183d2..8864dff8f8 100644 --- a/packages/plugins/plugin-audit/src/translations/en.objects.generated.ts +++ b/packages/plugins/plugin-audit/src/translations/en.objects.generated.ts @@ -121,6 +121,7 @@ export const enObjects: NonNullable = { }, type: { label: "Type", + help: "Activity kind. The declared options are the platform BUILT-IN set of an open vocabulary, not a closed enum: metadata authors may contribute their own values (sanctioned channel: `activityMilestones[].type`, ADR-0052 §5b.2), and an undeclared value is stored verbatim rather than rejected. Consumers must render an unknown value instead of assuming this list is exhaustive (maintainer ruling 2026-08-24, #11507).", options: { created: "created", updated: "updated", diff --git a/packages/plugins/plugin-audit/src/translations/es-ES.objects.generated.ts b/packages/plugins/plugin-audit/src/translations/es-ES.objects.generated.ts index 89135d97d3..d99850fab9 100644 --- a/packages/plugins/plugin-audit/src/translations/es-ES.objects.generated.ts +++ b/packages/plugins/plugin-audit/src/translations/es-ES.objects.generated.ts @@ -121,6 +121,7 @@ export const esESObjects: NonNullable = { }, type: { label: "Tipo", + help: "Activity kind. The declared options are the platform BUILT-IN set of an open vocabulary, not a closed enum: metadata authors legitimately contribute their own values — the sanctioned channel is `activityMilestones[].type` (ADR-0052 §5b.2) — and an undeclared value is stored verbatim rather than rejected, since every field on this object is readonly and option validation never runs on one. Read this list as what the platform itself writes, never as what the column may contain: consumers must render an unknown value instead of assuming the list is exhaustive (maintainer ruling 2026-08-24, #11507).", options: { created: "Creado", updated: "Actualizado", diff --git a/packages/plugins/plugin-audit/src/translations/ja-JP.objects.generated.ts b/packages/plugins/plugin-audit/src/translations/ja-JP.objects.generated.ts index dbf617dacf..55c5e359be 100644 --- a/packages/plugins/plugin-audit/src/translations/ja-JP.objects.generated.ts +++ b/packages/plugins/plugin-audit/src/translations/ja-JP.objects.generated.ts @@ -121,6 +121,7 @@ export const jaJPObjects: NonNullable = { }, type: { label: "タイプ", + help: "Activity kind. The declared options are the platform BUILT-IN set of an open vocabulary, not a closed enum: metadata authors legitimately contribute their own values — the sanctioned channel is `activityMilestones[].type` (ADR-0052 §5b.2) — and an undeclared value is stored verbatim rather than rejected, since every field on this object is readonly and option validation never runs on one. Read this list as what the platform itself writes, never as what the column may contain: consumers must render an unknown value instead of assuming the list is exhaustive (maintainer ruling 2026-08-24, #11507).", options: { created: "作成", updated: "更新", diff --git a/packages/plugins/plugin-audit/src/translations/zh-CN.objects.generated.ts b/packages/plugins/plugin-audit/src/translations/zh-CN.objects.generated.ts index 109cb1212f..54ae95aceb 100644 --- a/packages/plugins/plugin-audit/src/translations/zh-CN.objects.generated.ts +++ b/packages/plugins/plugin-audit/src/translations/zh-CN.objects.generated.ts @@ -121,6 +121,7 @@ export const zhCNObjects: NonNullable = { }, type: { label: "类型", + help: "Activity kind. The declared options are the platform BUILT-IN set of an open vocabulary, not a closed enum: metadata authors legitimately contribute their own values — the sanctioned channel is `activityMilestones[].type` (ADR-0052 §5b.2) — and an undeclared value is stored verbatim rather than rejected, since every field on this object is readonly and option validation never runs on one. Read this list as what the platform itself writes, never as what the column may contain: consumers must render an unknown value instead of assuming the list is exhaustive (maintainer ruling 2026-08-24, #11507).", options: { created: "已创建", updated: "已更新", From a79cc3bf6b9f733979d60b88f9e9943e079abc66 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 24 Aug 2026 11:19:23 +0000 Subject: [PATCH 2/2] fix(audit): heal the three non-en help leaves left holding a superseded draft (#11507) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `--fill=default` is fill-only: the first extract seeded es-ES/ja-JP/zh-CN from an earlier wording of the `sys_activity.type` description, and the re-extract after the wording was revised left those non-empty leaves alone — so the three carried two clauses the declaration no longer makes. Dropped the stale leaves and re-ran the extract config's own documented command; all four now match the source string byte for byte. No hand-edited translation text. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01APWX2AwT3a4xDcjPCe8bk4 --- .../plugin-audit/src/translations/es-ES.objects.generated.ts | 2 +- .../plugin-audit/src/translations/ja-JP.objects.generated.ts | 2 +- .../plugin-audit/src/translations/zh-CN.objects.generated.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/plugins/plugin-audit/src/translations/es-ES.objects.generated.ts b/packages/plugins/plugin-audit/src/translations/es-ES.objects.generated.ts index d99850fab9..ebb9cffe40 100644 --- a/packages/plugins/plugin-audit/src/translations/es-ES.objects.generated.ts +++ b/packages/plugins/plugin-audit/src/translations/es-ES.objects.generated.ts @@ -121,7 +121,7 @@ export const esESObjects: NonNullable = { }, type: { label: "Tipo", - help: "Activity kind. The declared options are the platform BUILT-IN set of an open vocabulary, not a closed enum: metadata authors legitimately contribute their own values — the sanctioned channel is `activityMilestones[].type` (ADR-0052 §5b.2) — and an undeclared value is stored verbatim rather than rejected, since every field on this object is readonly and option validation never runs on one. Read this list as what the platform itself writes, never as what the column may contain: consumers must render an unknown value instead of assuming the list is exhaustive (maintainer ruling 2026-08-24, #11507).", + help: "Activity kind. The declared options are the platform BUILT-IN set of an open vocabulary, not a closed enum: metadata authors may contribute their own values (sanctioned channel: `activityMilestones[].type`, ADR-0052 §5b.2), and an undeclared value is stored verbatim rather than rejected. Consumers must render an unknown value instead of assuming this list is exhaustive (maintainer ruling 2026-08-24, #11507).", options: { created: "Creado", updated: "Actualizado", diff --git a/packages/plugins/plugin-audit/src/translations/ja-JP.objects.generated.ts b/packages/plugins/plugin-audit/src/translations/ja-JP.objects.generated.ts index 55c5e359be..3d5e41290c 100644 --- a/packages/plugins/plugin-audit/src/translations/ja-JP.objects.generated.ts +++ b/packages/plugins/plugin-audit/src/translations/ja-JP.objects.generated.ts @@ -121,7 +121,7 @@ export const jaJPObjects: NonNullable = { }, type: { label: "タイプ", - help: "Activity kind. The declared options are the platform BUILT-IN set of an open vocabulary, not a closed enum: metadata authors legitimately contribute their own values — the sanctioned channel is `activityMilestones[].type` (ADR-0052 §5b.2) — and an undeclared value is stored verbatim rather than rejected, since every field on this object is readonly and option validation never runs on one. Read this list as what the platform itself writes, never as what the column may contain: consumers must render an unknown value instead of assuming the list is exhaustive (maintainer ruling 2026-08-24, #11507).", + help: "Activity kind. The declared options are the platform BUILT-IN set of an open vocabulary, not a closed enum: metadata authors may contribute their own values (sanctioned channel: `activityMilestones[].type`, ADR-0052 §5b.2), and an undeclared value is stored verbatim rather than rejected. Consumers must render an unknown value instead of assuming this list is exhaustive (maintainer ruling 2026-08-24, #11507).", options: { created: "作成", updated: "更新", diff --git a/packages/plugins/plugin-audit/src/translations/zh-CN.objects.generated.ts b/packages/plugins/plugin-audit/src/translations/zh-CN.objects.generated.ts index 54ae95aceb..f33475c9ba 100644 --- a/packages/plugins/plugin-audit/src/translations/zh-CN.objects.generated.ts +++ b/packages/plugins/plugin-audit/src/translations/zh-CN.objects.generated.ts @@ -121,7 +121,7 @@ export const zhCNObjects: NonNullable = { }, type: { label: "类型", - help: "Activity kind. The declared options are the platform BUILT-IN set of an open vocabulary, not a closed enum: metadata authors legitimately contribute their own values — the sanctioned channel is `activityMilestones[].type` (ADR-0052 §5b.2) — and an undeclared value is stored verbatim rather than rejected, since every field on this object is readonly and option validation never runs on one. Read this list as what the platform itself writes, never as what the column may contain: consumers must render an unknown value instead of assuming the list is exhaustive (maintainer ruling 2026-08-24, #11507).", + help: "Activity kind. The declared options are the platform BUILT-IN set of an open vocabulary, not a closed enum: metadata authors may contribute their own values (sanctioned channel: `activityMilestones[].type`, ADR-0052 §5b.2), and an undeclared value is stored verbatim rather than rejected. Consumers must render an unknown value instead of assuming this list is exhaustive (maintainer ruling 2026-08-24, #11507).", options: { created: "已创建", updated: "已更新",