Skip to content

Strengthen the shared conformance contract - #268

Merged
nicklambourne merged 74 commits into
masterfrom
codex/fix-conformance
Aug 14, 2026
Merged

Strengthen the shared conformance contract#268
nicklambourne merged 74 commits into
masterfrom
codex/fix-conformance

Conversation

@nicklambourne

Copy link
Copy Markdown
Owner

Scope

Releases spec 1.0.1 and makes the conformance contract enforce what it claims: Python now builds every valid fixture generatively from a construction registry (replacing the grep-based check that a comment could satisfy), both languages enumerate their public API against the capability registry so a new block cannot ship without conformance coverage, limits verified against live Slack documentation (option.value raised 75 → 150; the overflow-menu minimum was checked and left at 1 as documented), new shared limits for checkbox/radio option counts and option/URL-source URLs, structural invalid cases for table blocks, at-limit and astral-plane-emoji boundary fixtures that pin the code-point counting policy, and SPEC/ADR/PRINCIPLES rewritten to describe the actual skiplist mechanism, the normative status of limits.json, and the deliberate block_id divergence.

Stack

  • Train: Review remediation
  • Step: 3 of 5
  • Base: codex/fix-python
  • This is a stacked draft; review and merge it only after its predecessor.
  • After the predecessor is merged, retarget this PR to master before merging it.

Validation

641 Python tests at 93.19 percent coverage, mypy, Ruff, and 269 TypeScript tests with typecheck passed locally.

Nicholas Lambourne and others added 30 commits August 14, 2026 02:52
Run push-triggered workflows only for master (PR branches already get
pull_request runs, and stacked branches previously got neither), and drop
the pull_request branches filter so pull requests targeting stacked train
branches receive checks before they are retargeted to master.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nicholas Lambourne and others added 25 commits August 14, 2026 14:11
The historical-docs converter (now TOOL_VERSION 2) had three fidelity
defects, all fixed at the converter and baked into regenerated snapshots
for all 22 versions:

- Raw JSX <img> tags kept site-root src attributes, so every legacy
  index page's logo 404'd on GitHub Pages (Docusaurus only rewrites
  markdown-syntax images). Site-root srcs now get the /slackblocks/
  baseUrl, matching the current docs' hardcoded logo path.
- Link/.md rewrites ran inside inline code spans and fenced code blocks,
  corrupting literal examples (e.g. contributing pages showed a rewritten
  link while telling readers to write the unrewritten form). Code spans
  and fences are now masked during markdown rewriting.
- MkDocs tab labels carried literal backticks into TabItem label
  attributes; backticks are stripped.

The generator also now preserves any newer non-legacy prefix when it
rewrites docs/versions.json, derives validation from the manifest instead
of hardcoded 22/294 counts, and records its tool version in the manifest.

Regenerated diff: 294 provenance-comment updates, 277 tab-label fixes,
22 logo URL prefixes, and 4 restored code-span lines; nothing else.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The contract checker no longer hardcodes 22 versions / 294 routes; all
counts derive from docs/legacy/manifest.json, and docs/versions.json must
now end with the registered legacy versions in manifest order, so a future
`docusaurus docs:version 2.1` can prepend without breaking the build. The
asset-existence check understands baseUrl-prefixed image targets.

Every legacy version is marked noIndex so 22 near-duplicate historical
snapshots stop competing with the current docs in external search; the
built-site contract now asserts the robots noindex meta alongside the
existing canonical-URL assertion. The local search theme sets
forceIgnoreNoIndex so the version-scoped in-site search (also under
contract) keeps covering legacy pages.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
test:legacy-fixture existed but nothing invoked it; the docs build now
runs it before the legacy contract check, so the CI workflow picks it up
without changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Documents the frozen manifest/inventories/fixture, the regeneration
workflow when the snapshot byte-compare fails in CI (including when to
bump TOOL_VERSION), and the versions.json forward-compatibility rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…atim

The wire serializer previously let a caller-supplied 'type' key override the
factory's type, passed null values through to the wire (Python drops None),
and snake-cased user-defined message-metadata payload keys. Factories now
always win the 'type' field, null values are dropped exactly like undefined,
and the value under 'event_payload' is deep-copied verbatim with no key
renaming. Also adds the code-point length and empty-collection helpers used
by subsequent validation and parity changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The exported RangeError class shadowed the global RangeError, making
instanceof checks and catch clauses ambiguous. The class keeps its
'out-of-range' category string. No back-compat alias is provided because the
package is unpublished.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Raw-JSON validation previously accepted structurally incomplete objects such
as {type:"header"} with no text. validateKnownObject now enforces the
required fields every factory enforces, validates type-less option,
option-group, and confirmation objects contextually through their typed
parents, and adds the previously missing 'table' block rules mirrored from
the Python TableBlock (row/column limits, rectangular rows, allowed cell
types, column_settings count).

Also in this change:
- character limits count Unicode code points instead of UTF-16 code units,
  matching Python's len() semantics for emoji and other astral-plane text
- non-finite numbers (NaN/Infinity) are rejected anywhere in a validated
  payload
- 'file_input' and 'timepicker' join the input-block element allowlist
- data_table captions must be non-empty, and an absent caption now reports
  missing-required instead of type-mismatch
- checkboxes and radio_buttons enforce 1-10 options
- image objects require alt_text and one of image_url or slack_file
- message metadata event_payload contents are skipped as opaque user data
- the validate() JSDoc documents the remaining asymmetries with factory
  validation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces hardcoded confirmation, option, option-group, and chart limit
literals in the composition-object factories with the values imported from
spec/limits.json, so a registry change cannot silently diverge from the
factories. No effective limit changes.

Also:
- option() enforces the 3000-character URL limit (Python parity)
- ensureLength counts Unicode code points
- axisChart's bespoke pre-validation is removed in favor of the shared
  series-chart validator that already runs through create()

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Element input interfaces previously extended Record<string, unknown>, which
destroyed TypeScript excess-property checking and allowed callers to smuggle
arbitrary fields (including 'type') past the compiler. The index-signature
inheritance is removed, so unknown input fields are now compile errors; the
unused ElementInput and ActionInput exports are gone.

Also in this change:
- imageBlock altText is required (type level; the validator enforces it at
  runtime), matching Slack's alt_text requirement
- richTextList style is required (type + runtime) and the docstring no
  longer claims a never-applied 'bullet' default
- empty optional collections are omitted from the wire where the Python
  implementation drops them: sectionBlock fields, static select options and
  option groups, and the initial selections of checkbox and multi-select
  elements
- iconButton and fileInput limits come from spec/limits.json
- the inputBlock parameter is named 'input' like every other factory

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
attachment() now validates its color as a hex code, mirroring the Python
implementation: '#rrggbb' passes through, bare 'rrggbb' is normalized to
'#rrggbb', the Slack aliases good/warning/danger are accepted, and anything
else raises TypeMismatchError. A Color constant object mirroring the Python
Color enum values is exported.

message() and messageResponse() drop empty blocks/attachments arrays and
attachment() drops an empty blocks array, matching the Python truthiness
serialization; webhookMessage() keeps empty arrays because Python serializes
them there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds regression coverage for metadata event_payload opacity, factory type
ownership, compile-time excess-property rejection, input-block element
allowlist additions, required alt text and list style, table-block rules,
raw-JSON required-field and composition-object validation, code-point
length limits, empty-collection omission parity, null dropping, non-finite
number rejection, checkbox/radio option counts, option URL limits, the
OutOfRangeError rename, attachment colors, data-table captions, and the
card/container text-coercion defaults.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a shared validate_type() utility (with allow_none overloads matching
the Text.to_text precedent) so blocks, elements, and objects can reject
wrongly-typed scalar fields at construction time with the library's
standard TypeMismatchError / MissingRequiredError taxonomy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- FeedbackButtons now rejects non-FeedbackButton positive_button /
  negative_button values at construction time.
- IconButton now rejects non-ConfirmationDialogue confirm values.
- URLSource.url must now be non-empty and at most 3000 characters,
  consistent with the Button.url convention.
- CheckboxGroup now enforces the Slack cap of 1-10 options (the same
  limit already enforced for RadioButtonGroup).
- Full Google-style docstrings for FeedbackButton, FeedbackButtons,
  IconButton, and URLSource, matching the established format.

action_id on FeedbackButtons and IconButton remains optional: the live
Slack reference (feedback-buttons-element and icon-button-element pages)
marks action_id as Optional for both, as it does icon_button.value.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
RawNumber, SlackIcon, ChartSegment, DataPoint, DataSeries, AxisConfig,
PieChart, and the axis charts (BarChart, AreaChart, LineChart) were
plain classes falling back to the default object repr while every other
public class reprs as pretty-printed JSON. They now subclass
RenderableMixin (following the FeedbackButton precedent); wire output is
unchanged.

Also validates that axis charts receive an AxisConfig for axis_config,
and adds full Google-style docstrings to the chart composition classes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- CardBlock now rejects non-Image hero_image / icon values (including
  ImageBlock, which is a block, not an element) and non-SlackIcon
  slack_icon values.
- ContainerBlock now rejects non-Image icon and non-RichTextBlock
  rich_text_title values.
- TaskCardBlock now rejects non-RichTextBlock details / output values.
- TimePicker added to ALLOWED_INPUT_ELEMENTS: Slack permits timepicker
  elements inside input blocks.
- Block.from_dict docstring now accurately lists which block types
  round-trip and which raise NotImplementedError.
- Full Google-style docstrings for the nine block classes added in
  the 2.1.0 release.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- AlertLevel, Chart, ContainerWidth, SlackIconName, and TaskStatus (the
  type aliases appearing in public signatures) are now importable from
  the top-level package.
- SPEC_VERSION declares the shared conformance spec version (1.0.0),
  mirroring the TypeScript package's specVersion export as required by
  the spec PRINCIPLES.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Records four user-visible behaviour changes that shipped undocumented in
2.1.0: FileInput emitting its type field, HeaderBlock length-checking and
plaintext-coercing pre-built Text objects, InputBlock accepting FileInput
elements, and over-long block_id values raising LengthError.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verify the spec's contested limits against live docs.slack.dev and align
the corpus and both implementations:

- option.value.max_length corrected from 75 to 150 per the option-object
  page; Python's hardcoded check updated, TypeScript already reads the
  shared registry.
- Overflow-menu options minimum verified: Slack documents only "up to
  five option objects" with no minimum, so min_items stays 1.
- Register limits both implementations already enforce: checkboxes
  options 1-10, radio_buttons options 1-10, option.url <= 3000, and
  url_source.url 1-3000 (TypeScript previously lacked the URL-source
  check; added to the validator with unit coverage).
- New invalid cases for every new scalar leaf plus table structural
  rules (row cap, ragged rows, column-settings mismatch) and an
  emoji-based over-limit case pinning code-point counting.
- New at-limit valid fixtures (since 1.0.1): header text at 150 chars,
  header text of 150 astral-plane emoji code points, button text at 75
  chars, option value at 150 chars.
- blocks/table_block now pins an explicit block_id so both languages
  construct it identically.
- Spec version bumped to 1.0.1 in the registries and in both declared
  constants (specVersion / SPEC_VERSION).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…egistry

Replace Python's grep-based valid-fixture enforcement with a generative
harness mirroring the TypeScript approach: a registry maps every
manifest fixture ID to a construction through the public API, and a
parametrised test asserts the rendered JSON matches the fixture, so an
unregistered manifest entry or a divergent construction now fails.

Add export-enumeration capability enforcement in both languages: each
suite enumerates its package's public JSON-producing symbols (classes in
Python, factory functions in TypeScript) and asserts a bidirectional
match with spec/coverage.json, modulo an explicit commented exclusions
list (errors, enums, abstract bases, Text helpers, utilities).

Both suites now assert the declared spec version (SPEC_VERSION /
specVersion) equals the manifest's spec_version, and both exercise the
new 1.0.1 invalid cases and at-limit fixtures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cies

- SPEC.md: replace the fictional unknown-entry/stale-skip machinery with
  the real mechanism (skiplists must be empty in released states; entries
  are temporary during multi-PR transitions; both harnesses hard-assert
  emptiness), and document the generative fixture and coverage
  enforcement.
- ADR.md: state that limits.json is normative and test-enforced (every
  scalar leaf forces an invalid case in both languages) rather than
  aspirational; implementations may hardcode, tests pin agreement.
- PRINCIPLES.md: document the intentional policies - character limits
  count Unicode code points in both implementations, and Python
  auto-generates a UUID block_id while TypeScript omits it (fixtures pin
  explicit block_ids); name the declared spec-version constants.
- CHANGELOG.md: add the 1.0.1 entry covering the docs-verified limit
  corrections and the new boundary/unicode corpus.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Windows CI decodes with cp1252 by default, which turned the emoji
boundary fixture into mojibake.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@nicklambourne
nicklambourne marked this pull request as ready for review August 14, 2026 13:02
@nicklambourne
nicklambourne changed the base branch from codex/fix-python to master August 14, 2026 13:02
@nicklambourne
nicklambourne merged commit ed8b6b3 into master Aug 14, 2026
21 checks passed
@nicklambourne
nicklambourne deleted the codex/fix-conformance branch August 14, 2026 13:35
nicklambourne added a commit that referenced this pull request Aug 17, 2026
* chore: move Python package into workspace

* test: establish shared conformance contract

* feat: add TypeScript package

* ci: add coordinated package release automation

* docs: migrate site to Docusaurus

* feat: make docs language selection site-wide

* docs: complete language-specific guides

* feat: expose versioned documentation

* feat: standardise TypeScript block factory names

* docs: improve API reference generation and navigation

* test: enforce comprehensive conformance coverage

* feat: support current Block Kit blocks in Python

* feat: support current Block Kit blocks in TypeScript

* docs: bring TypeScript API reference to parity

* docs: add validated Block Kit previews

* ci: align monorepo validation

* chore: prepare 2.1.0 release

* docs: add legacy migration contract and tooling

* docs: port early 1.0 documentation

* docs: port later 1.0 and 1.1 documentation

* docs: port 1.2 documentation

* docs: port 2.0 documentation and version boundaries

* docs: harden historical documentation delivery

* docs: make version selection language-aware

* chore: scope TypeScript npm package

* ci: give stacked pull requests check runs

Run push-triggered workflows only for master (PR branches already get
pull_request runs, and stacked branches previously got neither), and drop
the pull_request branches filter so pull requests targeting stacked train
branches receive checks before they are retargeted to master.


* ci: give stacked pull requests check runs


* ci: give stacked pull requests check runs


* fix(docs): repair legacy converter fidelity and regenerate snapshots

The historical-docs converter (now TOOL_VERSION 2) had three fidelity
defects, all fixed at the converter and baked into regenerated snapshots
for all 22 versions:

- Raw JSX <img> tags kept site-root src attributes, so every legacy
  index page's logo 404'd on GitHub Pages (Docusaurus only rewrites
  markdown-syntax images). Site-root srcs now get the /slackblocks/
  baseUrl, matching the current docs' hardcoded logo path.
- Link/.md rewrites ran inside inline code spans and fenced code blocks,
  corrupting literal examples (e.g. contributing pages showed a rewritten
  link while telling readers to write the unrewritten form). Code spans
  and fences are now masked during markdown rewriting.
- MkDocs tab labels carried literal backticks into TabItem label
  attributes; backticks are stripped.

The generator also now preserves any newer non-legacy prefix when it
rewrites docs/versions.json, derives validation from the manifest instead
of hardcoded 22/294 counts, and records its tool version in the manifest.

Regenerated diff: 294 provenance-comment updates, 277 tab-label fixes,
22 logo URL prefixes, and 4 restored code-span lines; nothing else.


* feat(docs): future-proof legacy contract and noindex historical versions

The contract checker no longer hardcodes 22 versions / 294 routes; all
counts derive from docs/legacy/manifest.json, and docs/versions.json must
now end with the registered legacy versions in manifest order, so a future
`docusaurus docs:version 2.1` can prepend without breaking the build. The
asset-existence check understands baseUrl-prefixed image targets.

Every legacy version is marked noIndex so 22 near-duplicate historical
snapshots stop competing with the current docs in external search; the
built-site contract now asserts the robots noindex meta alongside the
existing canonical-URL assertion. The local search theme sets
forceIgnoreNoIndex so the version-scoped in-site search (also under
contract) keeps covering legacy pages.


* chore(docs): run the legacy fixture determinism check in the docs build

test:legacy-fixture existed but nothing invoked it; the docs build now
runs it before the legacy contract check, so the CI workflow picks it up
without changes.


* docs: add legacy snapshot recovery runbook

Documents the frozen manifest/inventories/fixture, the regeneration
workflow when the snapshot byte-compare fails in CI (including when to
bump TOOL_VERSION), and the versions.json forward-compatibility rule.


* fix: stop type overrides, drop nulls, and copy metadata payloads verbatim

The wire serializer previously let a caller-supplied 'type' key override the
factory's type, passed null values through to the wire (Python drops None),
and snake-cased user-defined message-metadata payload keys. Factories now
always win the 'type' field, null values are dropped exactly like undefined,
and the value under 'event_payload' is deep-copied verbatim with no key
renaming. Also adds the code-point length and empty-collection helpers used
by subsequent validation and parity changes.


* refactor!: rename RangeError to OutOfRangeError

The exported RangeError class shadowed the global RangeError, making
instanceof checks and catch clauses ambiguous. The class keeps its
'out-of-range' category string. No back-compat alias is provided because the
package is unpublished.


* feat: validate required fields, tables, and code points in raw payloads

Raw-JSON validation previously accepted structurally incomplete objects such
as {type:"header"} with no text. validateKnownObject now enforces the
required fields every factory enforces, validates type-less option,
option-group, and confirmation objects contextually through their typed
parents, and adds the previously missing 'table' block rules mirrored from
the Python TableBlock (row/column limits, rectangular rows, allowed cell
types, column_settings count).

Also in this change:
- character limits count Unicode code points instead of UTF-16 code units,
  matching Python's len() semantics for emoji and other astral-plane text
- non-finite numbers (NaN/Infinity) are rejected anywhere in a validated
  payload
- 'file_input' and 'timepicker' join the input-block element allowlist
- data_table captions must be non-empty, and an absent caption now reports
  missing-required instead of type-mismatch
- checkboxes and radio_buttons enforce 1-10 options
- image objects require alt_text and one of image_url or slack_file
- message metadata event_payload contents are skipped as opaque user data
- the validate() JSDoc documents the remaining asymmetries with factory
  validation


* refactor: source composition limits from the shared registry

Replaces hardcoded confirmation, option, option-group, and chart limit
literals in the composition-object factories with the values imported from
spec/limits.json, so a registry change cannot silently diverge from the
factories. No effective limit changes.

Also:
- option() enforces the 3000-character URL limit (Python parity)
- ensureLength counts Unicode code points
- axisChart's bespoke pre-validation is removed in favor of the shared
  series-chart validator that already runs through create()


* feat!: tighten factory input types and required fields

Element input interfaces previously extended Record<string, unknown>, which
destroyed TypeScript excess-property checking and allowed callers to smuggle
arbitrary fields (including 'type') past the compiler. The index-signature
inheritance is removed, so unknown input fields are now compile errors; the
unused ElementInput and ActionInput exports are gone.

Also in this change:
- imageBlock altText is required (type level; the validator enforces it at
  runtime), matching Slack's alt_text requirement
- richTextList style is required (type + runtime) and the docstring no
  longer claims a never-applied 'bullet' default
- empty optional collections are omitted from the wire where the Python
  implementation drops them: sectionBlock fields, static select options and
  option groups, and the initial selections of checkbox and multi-select
  elements
- iconButton and fileInput limits come from spec/limits.json
- the inputBlock parameter is named 'input' like every other factory


* feat: validate attachment colors and mirror Python collection omission

attachment() now validates its color as a hex code, mirroring the Python
implementation: '#rrggbb' passes through, bare 'rrggbb' is normalized to
'#rrggbb', the Slack aliases good/warning/danger are accepted, and anything
else raises TypeMismatchError. A Color constant object mirroring the Python
Color enum values is exported.

message() and messageResponse() drop empty blocks/attachments arrays and
attachment() drops an empty blocks array, matching the Python truthiness
serialization; webhookMessage() keeps empty arrays because Python serializes
them there.


* test: cover serialization, validation, and parity fixes

Adds regression coverage for metadata event_payload opacity, factory type
ownership, compile-time excess-property rejection, input-block element
allowlist additions, required alt text and list style, table-block rules,
raw-JSON required-field and composition-object validation, code-point
length limits, empty-collection omission parity, null dropping, non-finite
number rejection, checkbox/radio option counts, option URL limits, the
OutOfRangeError rename, attachment colors, data-table captions, and the
card/container text-coercion defaults.


* feat: add validate_type helper for single-valued object fields

Adds a shared validate_type() utility (with allow_none overloads matching
the Text.to_text precedent) so blocks, elements, and objects can reject
wrongly-typed scalar fields at construction time with the library's
standard TypeMismatchError / MissingRequiredError taxonomy.


* feat: validate new context-actions elements and element limits

- FeedbackButtons now rejects non-FeedbackButton positive_button /
  negative_button values at construction time.
- IconButton now rejects non-ConfirmationDialogue confirm values.
- URLSource.url must now be non-empty and at most 3000 characters,
  consistent with the Button.url convention.
- CheckboxGroup now enforces the Slack cap of 1-10 options (the same
  limit already enforced for RadioButtonGroup).
- Full Google-style docstrings for FeedbackButton, FeedbackButtons,
  IconButton, and URLSource, matching the established format.

action_id on FeedbackButtons and IconButton remains optional: the live
Slack reference (feedback-buttons-element and icon-button-element pages)
marks action_id as Optional for both, as it does icon_button.value.


* fix: render chart composition objects as JSON in repr

RawNumber, SlackIcon, ChartSegment, DataPoint, DataSeries, AxisConfig,
PieChart, and the axis charts (BarChart, AreaChart, LineChart) were
plain classes falling back to the default object repr while every other
public class reprs as pretty-printed JSON. They now subclass
RenderableMixin (following the FeedbackButton precedent); wire output is
unchanged.

Also validates that axis charts receive an AxisConfig for axis_config,
and adds full Google-style docstrings to the chart composition classes.


* feat: validate object fields on new blocks and allow timepicker input

- CardBlock now rejects non-Image hero_image / icon values (including
  ImageBlock, which is a block, not an element) and non-SlackIcon
  slack_icon values.
- ContainerBlock now rejects non-Image icon and non-RichTextBlock
  rich_text_title values.
- TaskCardBlock now rejects non-RichTextBlock details / output values.
- TimePicker added to ALLOWED_INPUT_ELEMENTS: Slack permits timepicker
  elements inside input blocks.
- Block.from_dict docstring now accurately lists which block types
  round-trip and which raise NotImplementedError.
- Full Google-style docstrings for the nine block classes added in
  the 2.1.0 release.


* feat: export public type aliases and declare spec version

- AlertLevel, Chart, ContainerWidth, SlackIconName, and TaskStatus (the
  type aliases appearing in public signatures) are now importable from
  the top-level package.
- SPEC_VERSION declares the shared conformance spec version (1.0.0),
  mirroring the TypeScript package's specVersion export as required by
  the spec PRINCIPLES.


* docs: document 2.1.0 behaviour changes in changelog

Records four user-visible behaviour changes that shipped undocumented in
2.1.0: FileInput emitting its type field, HeaderBlock length-checking and
plaintext-coercing pre-built Text objects, InputBlock accepting FileInput
elements, and over-long block_id values raising LengthError.


* feat(spec): release 1.0.1 with docs-verified limits and boundary corpus

Verify the spec's contested limits against live docs.slack.dev and align
the corpus and both implementations:

- option.value.max_length corrected from 75 to 150 per the option-object
  page; Python's hardcoded check updated, TypeScript already reads the
  shared registry.
- Overflow-menu options minimum verified: Slack documents only "up to
  five option objects" with no minimum, so min_items stays 1.
- Register limits both implementations already enforce: checkboxes
  options 1-10, radio_buttons options 1-10, option.url <= 3000, and
  url_source.url 1-3000 (TypeScript previously lacked the URL-source
  check; added to the validator with unit coverage).
- New invalid cases for every new scalar leaf plus table structural
  rules (row cap, ragged rows, column-settings mismatch) and an
  emoji-based over-limit case pinning code-point counting.
- New at-limit valid fixtures (since 1.0.1): header text at 150 chars,
  header text of 150 astral-plane emoji code points, button text at 75
  chars, option value at 150 chars.
- blocks/table_block now pins an explicit block_id so both languages
  construct it identically.
- Spec version bumped to 1.0.1 in the registries and in both declared
  constants (specVersion / SPEC_VERSION).


* test(conformance): build fixtures generatively and enforce coverage registry

Replace Python's grep-based valid-fixture enforcement with a generative
harness mirroring the TypeScript approach: a registry maps every
manifest fixture ID to a construction through the public API, and a
parametrised test asserts the rendered JSON matches the fixture, so an
unregistered manifest entry or a divergent construction now fails.

Add export-enumeration capability enforcement in both languages: each
suite enumerates its package's public JSON-producing symbols (classes in
Python, factory functions in TypeScript) and asserts a bidirectional
match with spec/coverage.json, modulo an explicit commented exclusions
list (errors, enums, abstract bases, Text helpers, utilities).

Both suites now assert the declared spec version (SPEC_VERSION /
specVersion) equals the manifest's spec_version, and both exercise the
new 1.0.1 invalid cases and at-limit fixtures.


* docs(spec): describe the actual skiplist, limits, and divergence policies

- SPEC.md: replace the fictional unknown-entry/stale-skip machinery with
  the real mechanism (skiplists must be empty in released states; entries
  are temporary during multi-PR transitions; both harnesses hard-assert
  emptiness), and document the generative fixture and coverage
  enforcement.
- ADR.md: state that limits.json is normative and test-enforced (every
  scalar leaf forces an invalid case in both languages) rather than
  aspirational; implementations may hardcode, tests pin agreement.
- PRINCIPLES.md: document the intentional policies - character limits
  count Unicode code points in both implementations, and Python
  auto-generates a UUID block_id while TypeScript omits it (fixtures pin
  explicit block_ids); name the declared spec-version constants.
- CHANGELOG.md: add the 1.0.1 entry covering the docs-verified limit
  corrections and the new boundary/unicode corpus.


* fix: read spec files as UTF-8 regardless of platform locale

Windows CI decodes with cp1252 by default, which turned the emoji
boundary fixture into mojibake.


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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant