Skip to content

feat(spec,plugin-audit): publish the sys_activity.type built-in vocabulary from @objectstack/spec (#11807) - #12331

Merged
os-litant merged 1 commit into
mainfrom
claude/issue-11807-publish-activity-builtin-vocab
Aug 26, 2026
Merged

os-litant merged 1 commit into
mainfrom
claude/issue-11807-publish-activity-builtin-vocab

Conversation

@os-litant

@os-litant os-litant commented Aug 25, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #11807

What

sys_activity.type's built-in vocabulary existed only inside plugin-audit's object declaration, so UI packages hand-copied it — and the copy drifted the same day #11522 declared scheduled. This PR publishes the set where consumers can read it:

  • packages/spec/src/data/feed.zod.ts — new exports SYS_ACTIVITY_BUILTIN_TYPES (a plain as const tuple) and SysActivityBuiltinType (derived union), co-located with FeedItemType as the issue proposed. The TSDoc states the [Decision] Is sys_activity.type a closed platform vocabulary or an author-extensible one? Both readings are true of the code today #11507 boundary: this is the built-in set of an OPEN, author-extensible vocabulary, never the column's value domain — consumers must render unknown values, not drop or reject them. It is deliberately not a z.enum: a Zod schema here would read as a validator and quietly re-close the vocabulary.
  • packages/plugins/plugin-audit/src/objects/sys-activity.object.ts — the type field's options now spread the published constant instead of inlining the list (single source; parsed output is byte-identical, so the generated i18n bundles do not move).
  • sys-activity-type-vocabulary.test.ts — new wiring pin: the object's declared options must equal the published set, so a diverging re-inlined list cannot come back silently. The existing literal writer census is kept verbatim and now transitively pins the spec constant: editing the published set without redoing the census goes red there. Chain closed in both directions: spec constant === object declaration === writer census.
  • packages/spec/src/data/feed.test.ts — mechanical invariants for the new export (non-empty, unique, snake_case; plain tuple with no parse/safeParse affordance). The semantic census deliberately stays in plugin-audit — a second literal inventory in spec would be a second copy that can drift, which is what this card retires.
  • Regenerated (via check:generated --fix, only the four proved stale): api-surface/data.json, export-origins/data.json, content/docs/references/data/feed.mdx, skills/objectstack-ui/references/_index.md.
  • Changeset: @objectstack/spec minor (new public export), @objectstack/plugin-audit patch. Additive — no ADR-0087 disposition required (check:adr-0087-registration green).

Out of scope, unchanged here: objectui's census pin retires in the UI lane after the pin bump (objectui#5969 lineage; cross-repo mirror gap already tracked as #8852). #11507 remains the governing ruling — nothing here narrows activityMilestones[].type or adds enforcement, and #11522 stays untouched by this PR.

App-side extension interaction — measured, two-layer shape NOT needed

The #11807 dispatch asked whether the published set must be two-layer ("built-in + extensions"). Measured answer: no.

  1. Extension values are app-owned and runtime-only. The sanctioned channel activityMilestones[].type is z.string().optional() (object.zod.ts:2021) forwarded verbatim by audit-writers.ts:1328 (if (milestone.type) activityType = milestone.type); app actions insert directly (both measured sites live in hotcrm, per the census). Spec cannot enumerate those at build time — a published "extension layer" would be structurally empty or a lie.
  2. No consumer needs the union. In-repo, the only enumerators of the vocabulary are the object declaration (now derived) and the census test. The cross-repo consumer (objectui recordActivityFeed.ts) is an open map — ACTIVITY_TYPE_TO_FEED_TYPE, a readonly record keyed by arbitrary string whose values are optional FeedItemType entries (the angle-bracket generic is spelled in words here because the platform body sanitizer eats it) — with UNMAPPED_ACTIVITY_FEED_TYPE = 'system' fallback plus a one-shot diagnostic, so app-contributed values already render generically with no published list required.
  3. The single layer handles adoption. An app value colliding with a built-in name simply is that built-in; promoting an app value into the platform set is a deliberate spec edit + census redo (the scheduled/fix(plugin-audit): declare sys_activity.type 'scheduled' and record its writer #11522 precedent), which one layer expresses as an ordinary addition.

Governed surface note (generated, zero net growth)

gen:skill-refs synced one module-description line in skills/objectstack-ui/references/_index.md (the feed.zod.ts header line). Readings: changed file 56 → 56 lines; whole published catalog SKILL.md sum 10512 → 10512 lines (net 0). No hand edits to skills/**. Because the diff touches skills/**, this PR is human-merge only (Prime Directive #14) — do not queue, arm, or flip it out of draft.

Verification

All heavy commands ran through scripts/pm/os-verify-lock.sh; exits captured before any pipe; verdict lines quoted from the gates' own output. Union run at head 31528895:

  • pnpm --filter @objectstack/spec build — VERDICT command-exit 0; check-dts-emitted: @objectstack/spec - 34/34 declared declaration file(s) present.
  • pnpm --filter @objectstack/spec check:generated — re-run at this head after the reverse-verification rebuild: "All 14 generated artifacts are up to date." (verbatim); api-surface delta is exactly + SYS_ACTIVITY_BUILTIN_TYPES (const), + SysActivityBuiltinType (type), no removals.
  • pnpm --filter @objectstack/spec test — Test Files 428 passed (428), Tests 11403 passed (11403).
  • pnpm --filter @objectstack/spec typecheck — exit 0 (check:test-typecheck: OK).
  • pnpm --filter '@objectstack/plugin-audit^...' build — VERDICT command-exit 0.
  • pnpm --filter @objectstack/plugin-audit test — Test Files 22 passed (22), Tests 326 passed (326).
  • pnpm --filter @objectstack/plugin-audit typecheck — exit 0.
  • Gate battery (dispatch list ∪ dispatch-gates.mjs re-derivation on the actual diff): 50 gates green, listed in the dev report. check:dev-prereqs red is a fresh-worktree precondition (52/67 workspace packages unbuilt — none of them touched here; spec dist freshly built and hash-stamped), not a finding of this diff; CI builds the workspace before running it.
  • Reverse verification (single-source drift tripwire): adding a probe value to SYS_ACTIVITY_BUILTIN_TYPES turns the census set-equality test red while the wiring pin stays green (object follows spec); restoring turns everything green. plugin-audit's vitest aliases @objectstack/spec/* to source (vitest.config.ts), so no rebuild is involved in either leg; the mutation and its restoration were both confirmed on disk by grep before each run.

Generated by Claude Code

…ulary from @objectstack/spec

Export SYS_ACTIVITY_BUILTIN_TYPES / SysActivityBuiltinType from
@objectstack/spec/data (feed.zod.ts, beside FeedItemType) and derive the
sys_activity object's type options from it, so the built-in set has one
readable source instead of hand-copies that drift. The set is the built-in
floor of an open, author-extensible vocabulary — deliberately a plain tuple,
not a z.enum, so it cannot be used to re-close the column. Census test gains
a wiring pin (object options === published set); spec-side test pins the
mechanical invariants. Regenerated: api-surface, export-origins, docs
reference page, skill refs index (one generated description line).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NDGG54XF5gbTLdQzCtnaVV
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:data tests tooling labels Aug 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

3 anchor(s) derived from 2 changed package(s); no hand-written page names any of them. ⚠️ 2 changed file(s) yielded no anchor (packages/spec/api-surface/data.json, packages/spec/export-origins/data.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/api-surface/data.json, packages/spec/export-origins/data.json) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 128 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json b8419bd5a349c76ef133e176e8dbc65a8bf156e6 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 20ea09c2a8800624aab63d7c2208f37e9b483fe2 — the merge of head 3152889559caefbbc100515c0289246e247de54e into base b8419bd5a349c76ef133e176e8dbc65a8bf156e6, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 20ea09c2a8800624aab63d7c2208f37e9b483fe2 && git checkout 20ea09c2a8800624aab63d7c2208f37e9b483fe2
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b8419bd5a349c76ef133e176e8dbc65a8bf156e6 3152889559caefbbc100515c0289246e247de54e && git checkout -B drift-repro b8419bd5a349c76ef133e176e8dbc65a8bf156e6 && git merge --no-ff 3152889559caefbbc100515c0289246e247de54e

node scripts/docs-audit/affected-docs.mjs --json b8419bd5a349c76ef133e176e8dbc65a8bf156e6

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@os-litant
os-litant requested a review from os-zhuang August 25, 2026 18:52
@os-litant
os-litant marked this pull request as ready for review August 25, 2026 23:40
@os-litant
os-litant added this pull request to the merge queue Aug 25, 2026
Merged via the queue into main with commit 2d8dd8d Aug 26, 2026
44 checks passed
@os-litant
os-litant deleted the claude/issue-11807-publish-activity-builtin-vocab branch August 26, 2026 00:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sys_activity.type's built-in vocabulary is unreadable outside plugin-audit, so UI pins hand-copy it (and the copy has already drifted)

2 participants