Skip to content

feat(spec): open RecordActivityProps.types to author-contributed activity kinds - #12408

Merged
huangyiirene merged 1 commit into
mainfrom
claude/issue-11658-activity-types-open
Aug 26, 2026
Merged

feat(spec): open RecordActivityProps.types to author-contributed activity kinds#12408
huangyiirene merged 1 commit into
mainfrom
claude/issue-11658-activity-types-open

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Fixes #11658

What

RecordActivityProps.types — the authorable record:activity feed filter, also embedded as RecordChatterProps.feed.types (the feed prop delegates to RecordActivityProps, so the same change propagates there) — was a closed z.array(FeedItemType) over the vocabulary the 2026-08-24 maintainer ruling on #11507 declared OPEN and author-extensible (verbatim: "every closed map over this vocabulary is now the bug"). The element is now:

z.union([FeedItemType, z.string().min(1)])
  • Open: the union accepts exactly what a bare non-empty string accepts — nothing is validated against the built-in set, so an author-contributed activity kind (scheduled, which hotcrm writes today, or any future kind authored through ADR-0052 §5b.2) is nameable in the filter.
  • Guided: the FeedItemType branch keeps the 13 built-in kinds visible as guidance — editor autocomplete, and an anyOf member of the generated JSON Schema. Measured, not assumed: json-schema/ui/RecordActivityProps.json (generated, gitignored tree) now emits items.anyOf = [enum(13 built-ins), string(minLength 1)], which is how JSON-schema-driven authoring tools keep suggesting the built-ins without re-closing the domain.
  • Accepted cost (named by the ruling's logic and this card's dispatch): a typo'd built-in no longer gets a named rejection — an open vocabulary cannot distinguish a typo from a contributed kind. Non-string and empty entries are still rejected.

Also in scope (flagged on the card 2026-08-24T11:07Z): the feed.zod.ts module docblock no longer claims these enums are "pure UI configuration … with no backend dependency" — FeedItemType is the TARGET of the map UI consumers apply to the open sys_activity.type column: a backend coupling, not a backend import. The generated reference pages (content/docs/references/data/feed.mdx including its "Allowed Values" framing context, and content/docs/references/ui/component.mdx for all three types rows) moved by regeneration only (check:generated --fix), never hand-edited.

Alignment with the landed upstream shape

Test-first verification (replaces mutate-and-restore ablation — no restore leg exists to fail silently)

New tests written on the unmodified schema with the failure predicted in writing first: the two open-vocabulary cases red, the rejection-preservation case green. Observed exactly that: Tests 2 failed | 223 passed (225), the two reds being accepts author-contributed activity kinds and does not reject a typo of a built-in kind by name. After the schema edit: Test Files 3 passed (3) · Tests 307 passed (307) (component + feed + page suites). The replaced closed-enum pin (should reject invalid feed item type) pinned exactly the branch the ruling removed — replaced, not reworded.

Verification — all at b451c43 (branch head, clean tree), heavy steps serialized through scripts/pm/os-verify-lock.sh, every exit code captured before any pipe

  • Targeted suites: Test Files 3 passed (3) · Tests 307 passed (307) — os-verify-lock VERDICT command-exit 0.
  • pnpm --filter @objectstack/spec build: VERDICT command-exit 0 (dist + DTS rebuilt; .build-input-hash refreshed).
  • pnpm --filter @objectstack/spec typecheck: VERDICT command-exit 0, including its own verdict line check:test-typecheck: OK — @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.json; 55 file(s) / 263 error(s) held in test-typecheck-debt.json (shrink-only) — the edited test file is inside that measured layer.
  • pnpm --filter @objectstack/spec check:generated: ✓ All 14 generated artifacts are up to date. (check:docs was the one proved stale; regenerated via --fix, diff reviewed.)
  • Whole-repo pnpm lint (eslint . --no-inline-config): VERDICT command-exit 0 — no narrowing needed.
  • Gate families derived from the real change set via node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack at b451c43 (43 path-matched + 5 convention-triggered for the test-file kind + check:nul-bytes): all exit 0, including check:authorable-surface, check:api-surface, check:slot-lookup, check:query-options-erasure, check:engine-double-contract, check:where-matcher, check:type-check-coverage, check:cross-package-test-inputs, check:changeset-no-major, check:adr-0087-registration, check:nul-bytes. Three gates (check:doc-formula-expressions, check:doc-security-posture, check:skill-examples) first returned prerequisite-not-met ("nothing was measured"); their named prerequisites were built (turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --filter=@objectstack/client-react, 33 tasks, VERDICT command-exit 0) and all three re-ran green (✅ 260 prose examples type-check across 3 surface(s)).
  • Declared NOT MEASURED (narrowing, not skipped-in-silence): check:type-check-debt --re-measure and check:dev-prereqs both require the whole packages/** closure built (65 packages) — a farm-scale run CI's lint.yml performs on every PR after its own full build. Evidence the narrowing excludes nothing of this diff: ① their populations are read from the gates' own output (per-package debt ledger entries; dev-stack dist presence — a container build-state property, not a diff property); ② this diff touches only packages/spec (built and typechecked clean at head, test layer included per the verdict line above), generated docs, and a changeset; ③ the repo-wide grep below shows zero downstream consumers whose tsc programs could move.

Consumer radius

Grep of all packages/, apps/, examples/ for RecordActivityProps / FeedItemType outside the two spec sources (direction: downstream/consumers): zero — only the spec sources themselves, the type-alias-convention pin (asserts z.input ≡ z.infer on FeedItemType, which is unchanged), and generated artifacts. No example or fixture authors record:activity types, so no fixture triage was owed. The cross-repo consumer is objectui's feed-kind map, pinned by its own queued card.

Changeset

.changeset/record-activity-types-open-vocabulary.md@objectstack/spec: minor, pure accept-set widening (every previously-legal value parses byte-identically), with an ADR-0087 not-required (no-migration-prescription) disposition note.


Generated by Claude Code

…vity kinds

Fixes #11658

Executes the 2026-08-24 maintainer ruling on #11507 (direction 4: the
sys_activity.type vocabulary is OPEN and author-extensible; every closed
map over it is the bug) on the authoring surface: the record:activity
types filter element widens from a closed z.array(FeedItemType) to
z.union([FeedItemType, z.string().min(1)]) — built-in kinds stay visible
as guidance (autocomplete / JSON-Schema anyOf), author-contributed kinds
become nameable, nothing is validated against the built-in set. Also
corrects the feed.zod.ts docblock's 'no backend dependency' framing and
regenerates the derived docs pages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5LFCYBJ3q2s6yW6oMLxwy
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 1 changed file(s) yielded no anchor (packages/spec/src/data/feed.zod.ts), 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
  • 1 changed file(s) yielded no anchor (packages/spec/src/data/feed.zod.ts) — 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 — 126 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 b000ab59bba509530595b1f2fb9604a4046ed55fpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 8173cebffa09181ee0e6a243132893bea87f0983 — the merge of head b451c43db21de6419aa6ada1fa84b1ea0e98467a into base b000ab59bba509530595b1f2fb9604a4046ed55f, 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 8173cebffa09181ee0e6a243132893bea87f0983 && git checkout 8173cebffa09181ee0e6a243132893bea87f0983
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b000ab59bba509530595b1f2fb9604a4046ed55f b451c43db21de6419aa6ada1fa84b1ea0e98467a && git checkout -B drift-repro b000ab59bba509530595b1f2fb9604a4046ed55f && git merge --no-ff b451c43db21de6419aa6ada1fa84b1ea0e98467a

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

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

record:activity's authorable types filter is a CLOSED enum over a vocabulary #11507 just ruled open

2 participants