From 3fa616d856c9335794e1ef59b713881f6cfa6ce8 Mon Sep 17 00:00:00 2001 From: Cindy Zhang Date: Sat, 24 Oct 2026 21:57:54 -0700 Subject: [PATCH 1/3] spec(AST-017): no file path or subpath makes an API experimental (FR21 clarification, DEC-27) --- docs/specs/AST-017/spec.md | 98 ++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 36 deletions(-) diff --git a/docs/specs/AST-017/spec.md b/docs/specs/AST-017/spec.md index f57100d57ab6f..8433a8b33426d 100644 --- a/docs/specs/AST-017/spec.md +++ b/docs/specs/AST-017/spec.md @@ -106,7 +106,8 @@ affects_consumer_docs: [release-process, templates] "FR59", "DEC-25", "FR60", - "DEC-26" + "DEC-26", + "DEC-27" ] } } @@ -369,13 +370,19 @@ one removal merged ahead of the decision to allow it. canonical experimental boundary and marks it in both declarations and consumer documentation. On an existing stable component, experimental props and callbacks live inside one optional `experimental` prop object; omitting that object preserves - the component's stable defaults and behavior. Experimental hooks, functions, and - types use the owning component's `/experimental` import subpath and are not - re-exported from the package root or the component's ordinary subpath. Declarations + the component's stable defaults and behavior. Behavior and utility modules, such as + motion, gesture, and keyboard handling, are internal: they are never exported, + as experimental or otherwise. An experimental public API's status is + visible at the call site where it is used, carried by the name the caller + writes, not only in docs, declarations, or a file path; a component's + `experimental` prop object meets this, and any other deliberately + experimental public API meets it with an `experimental_` name prefix. No file path or subpath, for a + component or a module, conveys experimental status by itself. Declarations carry `@experimental`; authored docs carry machine-readable `stability: 'experimental'` metadata and state that the surface may change or be removed in a patch release. Prose-only warnings and reviewer knowledge do not - establish this boundary. + establish this boundary. Any public experimental API also carries its own + specification decision. - **FR22 — Experimental changes are patch-level and stay confined.** Adding, changing, renaming, or removing an explicitly experimental surface uses an `[experimental]` Changeset and a patch bump. An incompatibility confined to that @@ -392,8 +399,8 @@ one removal merged ahead of the decision to allow it. owner-approved change that removes the experimental marker, exposes the settled prop, callback, hook, function, or type on its normal stable surface, and uses a `[feat]` Changeset. From that release forward, FR1–FR8 protect the promoted surface. - Moving a component API out of the `experimental` prop object or moving an export - out of a component's `/experimental` subpath names the stable replacement and + Moving a component API out of the `experimental` prop object or removing an + export's experimental marking names the stable replacement and supplies a codemod when the rewrite is mechanical. Keeping a deprecated experimental alias for a transition is encouraged but is not itself part of the new stable promise. @@ -848,7 +855,9 @@ Public lifecycle classification remains in force with these operational effects: - touch work that changed fine-pointer behavior only as a side effect becomes touch-only with fine pointers unchanged, and a fine-pointer visual change proposed alongside touch work, such as a new modal backdrop dim, becomes its own design - decision (FR59). + decision (FR59); +- no file path or subpath marks a surface experimental and Core's behavior and + utility modules stay unexported (FR21). Main declares the next planned version, and Changeset admission uses that declaration to refuse `[breaking]` work on a patch plan. Pull-request lint does not compare the @@ -874,34 +883,34 @@ lands, maintainers apply its requirement in review and release approval. ## Verification -| Contract | Verification | Representative states | Mutation or failure expectation | -| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| FR1–FR3 | PR compatibility statement plus latest stable package inspection | released export, narrowed type, changed interaction default; unreleased and private surface; visual-only spacing, typography, layout styling, or default appearance; a new touch presentation or open-surface touch mode as a default | A change is labeled from diff size or possibility alone, a functional contract loss is hidden as visual-only, or a pixel-only change is tagged `[breaking]` even though callers need no source or configuration change, or such a touch default names no `presentation` value that restores the previous surface and mode | -| FR4–FR6 | Old-usage type/runtime/CLI regression test | alias retained, deprecation warning, broad rewrite, low-adoption caller | Contractual old usage fails despite a nonbreaking label, or risk is substituted for compatibility | -| FR7–FR8 | `pnpm check:changesets` (tag, bump, contributors, declared tier) plus FR8 migration review of `[breaking]` text | existing `[fix]`, `[feat]`, `[breaking]`, and `[experimental]` tags paired with compatible, deprecation, planned-removal, or incompatible-fix metadata | A new lifecycle replaces an established tag, tag/metadata/bump diverge, an incompatible correction uses bare `[fix]`, or migration is unusable | -| FR9–FR13 | CLI contract tests, response-schema/type snapshots, text projections, generated consumer docs, and template catalog/output tests | slug rename, metadata edit, source rebuild, optional field addition, command or schema change | Catalog data is frozen as API, a command/schema incompatibility is mislabeled as catalog-only, or a response field lacks a complete projection | -| FR12 | Minimum and representative supported-version tests plus manifest and release-note review | retained range, narrowed range, adapter, coordinated upgrade | An in-range combination breaks under a nonbreaking label, or release coordination hides the affected package or migration | -| FR14 | Help/manifest snapshots, public API and consumer docs, and focused contract tests | command, option, API/config, private rollout/test hook | Supported behavior is hidden, an environment variable changes behavior, or automation lacks a documented API | -| FR15 | Full manifest-derived command matrix, supported-consumer evidence, and scope-specific contract tests | global invariant, scoped command group, single command, programmatic API | A global control is a no-op for any command, has different meanings, or replaces a narrower owning surface | -| FR16–FR18 | Boundary inventory, hostile side-effect probes, response snapshots, and concurrent API tests | known and new extension, partial result, text/JSON/API parity, independent concurrent calls | A route bypasses the guarantee, omitted work looks complete, or one invocation changes another | -| FR19–FR20 | Proposal evidence with a regression fixture for the detection failure, plus composition and provenance tests | convention covers the case, detection fails, app plus two integrations, refusal, failing contribution, repeated load | A key ships without a reproduced detection failure, a contribution displaces the app or applies twice, a part of the effective value has no inspectable source, or a failure silently weakens protection | -| FR21–FR24 | Published-surface comparison, declaration/doc metadata checks, import-boundary checks, and Changeset classification review | experimental prop object, experimental subpath, patch evolution, promotion, unpublished adjacent field, stable warning behavior, integration theme | A prose-only marker excludes a stable API, adjacency falsely publishes a new field, an experimental export leaks through a stable path, a patch changes stable behavior, or a promoted API remains unprotected | -| FR25–FR27 | Stable-surface inventory and experimental-boundary matrix | package/API, CSS/token, CLI/config, persistence, build tooling, docs, whole Lab component | An unmarked experiment changes stable defaults or a stable surface bypasses compatibility review | -| FR28–FR31 | Declared-tier check: on a minor, the `[breaking]` Changeset describes the change (old usage, replacement, migration); on a patch, a breaking Changeset is refused and the deprecation ships instead; declaration/doc/build/CLI warning projections; old/new fixtures; dry-run/idempotence/migration tests; maintained-source and latest-stable upgrade evidence | whole component, prop/value, theme field/helper/token/target/source, CLI command, CLI option; old-only, new-only, both, static/dynamic/uncertain migration, text/JSON, development/production | A warning has no usable modernization route, production runtime changes for deprecation, maintained guidance teaches the old surface, a migration guesses, old behavior degrades early, cadence invents a wait, or one cleanup id covers multiple incompatible deltas | -| FR32–FR35 | Latest-stable victim fixture, pre-existing authority named in the Changeset or pull request, owner review, declared-tier check, and emergency attestation | victim-free `[fix]`, `[fix]` plus incompatible-fix metadata, coexistence, cleanup transition, critical-harm emergency | A released victim uses bare `[fix]`, a new Changeset tag replaces metadata, a correction record disagrees with its public-surface state, or urgency bypasses migration | -| FR36–FR40 | Owner-declared target, locked final-patch receipt, three-way delta classification against the merged `[breaking]` Changesets, and separated release notes | ordinary cadence, immediate pair, cleanup, release metadata, pre/post-lock compatible fix | Cadence becomes eligibility, the final patch is recut for bookkeeping, a feature or incompatible fix enters the incidental-fix lane, or release notes merge cleanup with fixes | -| FR41–FR44 | Schema validation, PR declaration, semantic stable/base/head comparisons, exact-main gate, and immutable publish/rollback receipt | duplicate ids, missing evidence, route/schema removal, old-client metadata, partial publish, safe rollback | A label passes without semantics, a delta maps zero or multiple times, fixed-group membership drifts, or a release rebuilds under one identity | -| FR45 | Docs route inventory, repository-reference checks, and Changeset review | catalog entry rename, former-route miss, stable command and JSON schema | Catalog routing is frozen as API, or a rename silently changes the contractual command or response schema | -| FR46–FR47 | Declared-tier Changeset admission, canary publication ordering, and release-versioning fixtures | owner increase/decrease, above/equal/below stable, split group, `[breaking]` on patch and minor plans | Pending Changesets derive the plan, `[breaking]` passes on a patch or fails on a minor, equality publishes a canary, or a lower/split declaration publishes instead of failing | -| FR48 | Category and classification admission under each declared tier | patch release with `[breaking]`, patch release with a deprecation, minor release with `[breaking]` | Incompatible work passes in a patch release, or a deprecation is refused in one | -| FR49 | Malformed and inconsistent declaration fixtures | disagreeing group versions, canary identifier, release version unlike the cut manifest, already-released version, skipped version, no stable tag | An untrustworthy or mismatched declaration is released | -| FR50 | Three-way squash merge-back fixtures bound to the immutable tag, branch head, and plan digest | exact branch outputs, frozen deletion, branch-only marker exclusion, post-cut Changeset/manifest edit, newer main declaration, merge conflict, regenerated output | Merge-back differs from release bytes, lands lifecycle files, consumes post-cut work, overwrites a newer main declaration or unrelated field, or chooses a version | -| FR51 | Cut-time successor-plan preparation plus manifest/pin/lockfile/compatibility-asset and lifecycle fixtures | default patch successor, explicitly named minor, abort before any tag or publication, immutable tag before merge-back, pre-advanced main plan, unrelated main work during equality | The plan merges before the cut version is non-reusable, survives a terminal cut with neither an immutable tag nor stable publication, skips the default patch, derives a minor from pending Changesets, splits pins/assets, or the prepared plan or merge-back lowers or duplicates an existing newer plan | -| FR52 | Promotion fixtures plus every checked-in source consumer run before and after codemod promotion | staged transform, promoted transform, missing source, ambiguous candidates, changed entry identity | Evidence hardcodes only `next/`, keeps a duplicate source, silently chooses among candidates, or passes when the promoted transform no longer exposes the same identity and entries | -| FR53 | Changeset classification review against the governing component record's accessibility evidence | a fix that replaces documented keyboard or focus behavior to meet an adopted APG pattern's required parts; a fix that changes a role, state, or accessible name to meet WCAG 2.2 A/AA; a fix that adds an optional prop | An accessibility fix is held for a minor, given a coexistence path, or classified `[breaking]`; or a change that needs an app-layer code edit, or that removes, renames, or narrows public surface, ships as an accessibility `[fix]` | -| FR54 | Changeset classification review against the surface's current record, then the same tier admission every Changeset receives | a CLI correction the CLI record classifies as `[fix]`; a CLI change the CLI record leaves undecided; a CLI change the CLI record classifies as `[breaking]` on a patch plan; a component change | A non-component change is classified by a rule this record restates instead of its surface record, a surface rule turns on risk or adoption, a surface-classified `[breaking]` change ships in a patch or a surface rule makes it patch-eligible, or a component change is classified by a surface record | -| FR59 | Pull request review of fine-pointer before-and-after evidence | a touch change that also fixes a defect fine-pointer users hit; a deliberate cross-device consistency change; a touch-only change; a new fine-pointer look proposed with touch work | A touch change alters fine-pointer behavior or appearance with neither reason, shows no fine-pointer before-and-after evidence, or ships a new fine-pointer look without its own owner decision | -| FR60 | Element counts per story at rest and open, fine and coarse pointer, against architecture INV7 | a surface component at rest; an open layer; a DOM reduction; a recorded exception | An element beyond INV7's budgets with no recorded owner exception, a hidden twin or second presentation, or a DOM reduction that removes a theme target or documented anatomy, or that is classified as breaking | +| Contract | Verification | Representative states | Mutation or failure expectation | +| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FR1–FR3 | PR compatibility statement plus latest stable package inspection | released export, narrowed type, changed interaction default; unreleased and private surface; visual-only spacing, typography, layout styling, or default appearance; a new touch presentation or open-surface touch mode as a default | A change is labeled from diff size or possibility alone, a functional contract loss is hidden as visual-only, or a pixel-only change is tagged `[breaking]` even though callers need no source or configuration change, or such a touch default names no `presentation` value that restores the previous surface and mode | +| FR4–FR6 | Old-usage type/runtime/CLI regression test | alias retained, deprecation warning, broad rewrite, low-adoption caller | Contractual old usage fails despite a nonbreaking label, or risk is substituted for compatibility | +| FR7–FR8 | `pnpm check:changesets` (tag, bump, contributors, declared tier) plus FR8 migration review of `[breaking]` text | existing `[fix]`, `[feat]`, `[breaking]`, and `[experimental]` tags paired with compatible, deprecation, planned-removal, or incompatible-fix metadata | A new lifecycle replaces an established tag, tag/metadata/bump diverge, an incompatible correction uses bare `[fix]`, or migration is unusable | +| FR9–FR13 | CLI contract tests, response-schema/type snapshots, text projections, generated consumer docs, and template catalog/output tests | slug rename, metadata edit, source rebuild, optional field addition, command or schema change | Catalog data is frozen as API, a command/schema incompatibility is mislabeled as catalog-only, or a response field lacks a complete projection | +| FR12 | Minimum and representative supported-version tests plus manifest and release-note review | retained range, narrowed range, adapter, coordinated upgrade | An in-range combination breaks under a nonbreaking label, or release coordination hides the affected package or migration | +| FR14 | Help/manifest snapshots, public API and consumer docs, and focused contract tests | command, option, API/config, private rollout/test hook | Supported behavior is hidden, an environment variable changes behavior, or automation lacks a documented API | +| FR15 | Full manifest-derived command matrix, supported-consumer evidence, and scope-specific contract tests | global invariant, scoped command group, single command, programmatic API | A global control is a no-op for any command, has different meanings, or replaces a narrower owning surface | +| FR16–FR18 | Boundary inventory, hostile side-effect probes, response snapshots, and concurrent API tests | known and new extension, partial result, text/JSON/API parity, independent concurrent calls | A route bypasses the guarantee, omitted work looks complete, or one invocation changes another | +| FR19–FR20 | Proposal evidence with a regression fixture for the detection failure, plus composition and provenance tests | convention covers the case, detection fails, app plus two integrations, refusal, failing contribution, repeated load | A key ships without a reproduced detection failure, a contribution displaces the app or applies twice, a part of the effective value has no inspectable source, or a failure silently weakens protection | +| FR21–FR24 | Published-surface comparison, declaration/doc metadata checks, import-boundary checks, and Changeset classification review | experimental prop object, experimental subpath, patch evolution, promotion, unpublished adjacent field, stable warning behavior, integration theme; a component or module `/experimental` subpath; an `experimental_` export; a behavior module | A prose-only marker excludes a stable API, adjacency falsely publishes a new field, an experimental export leaks through a stable path, a patch changes stable behavior, or a promoted API remains unprotected, a file path or subpath alone marks a surface experimental, a behavior or utility module or an internal module without a stable contract is exported, or an experimental public API's call site does not show its status in the name the caller writes | +| FR25–FR27 | Stable-surface inventory and experimental-boundary matrix | package/API, CSS/token, CLI/config, persistence, build tooling, docs, whole Lab component | An unmarked experiment changes stable defaults or a stable surface bypasses compatibility review | +| FR28–FR31 | Declared-tier check: on a minor, the `[breaking]` Changeset describes the change (old usage, replacement, migration); on a patch, a breaking Changeset is refused and the deprecation ships instead; declaration/doc/build/CLI warning projections; old/new fixtures; dry-run/idempotence/migration tests; maintained-source and latest-stable upgrade evidence | whole component, prop/value, theme field/helper/token/target/source, CLI command, CLI option; old-only, new-only, both, static/dynamic/uncertain migration, text/JSON, development/production | A warning has no usable modernization route, production runtime changes for deprecation, maintained guidance teaches the old surface, a migration guesses, old behavior degrades early, cadence invents a wait, or one cleanup id covers multiple incompatible deltas | +| FR32–FR35 | Latest-stable victim fixture, pre-existing authority named in the Changeset or pull request, owner review, declared-tier check, and emergency attestation | victim-free `[fix]`, `[fix]` plus incompatible-fix metadata, coexistence, cleanup transition, critical-harm emergency | A released victim uses bare `[fix]`, a new Changeset tag replaces metadata, a correction record disagrees with its public-surface state, or urgency bypasses migration | +| FR36–FR40 | Owner-declared target, locked final-patch receipt, three-way delta classification against the merged `[breaking]` Changesets, and separated release notes | ordinary cadence, immediate pair, cleanup, release metadata, pre/post-lock compatible fix | Cadence becomes eligibility, the final patch is recut for bookkeeping, a feature or incompatible fix enters the incidental-fix lane, or release notes merge cleanup with fixes | +| FR41–FR44 | Schema validation, PR declaration, semantic stable/base/head comparisons, exact-main gate, and immutable publish/rollback receipt | duplicate ids, missing evidence, route/schema removal, old-client metadata, partial publish, safe rollback | A label passes without semantics, a delta maps zero or multiple times, fixed-group membership drifts, or a release rebuilds under one identity | +| FR45 | Docs route inventory, repository-reference checks, and Changeset review | catalog entry rename, former-route miss, stable command and JSON schema | Catalog routing is frozen as API, or a rename silently changes the contractual command or response schema | +| FR46–FR47 | Declared-tier Changeset admission, canary publication ordering, and release-versioning fixtures | owner increase/decrease, above/equal/below stable, split group, `[breaking]` on patch and minor plans | Pending Changesets derive the plan, `[breaking]` passes on a patch or fails on a minor, equality publishes a canary, or a lower/split declaration publishes instead of failing | +| FR48 | Category and classification admission under each declared tier | patch release with `[breaking]`, patch release with a deprecation, minor release with `[breaking]` | Incompatible work passes in a patch release, or a deprecation is refused in one | +| FR49 | Malformed and inconsistent declaration fixtures | disagreeing group versions, canary identifier, release version unlike the cut manifest, already-released version, skipped version, no stable tag | An untrustworthy or mismatched declaration is released | +| FR50 | Three-way squash merge-back fixtures bound to the immutable tag, branch head, and plan digest | exact branch outputs, frozen deletion, branch-only marker exclusion, post-cut Changeset/manifest edit, newer main declaration, merge conflict, regenerated output | Merge-back differs from release bytes, lands lifecycle files, consumes post-cut work, overwrites a newer main declaration or unrelated field, or chooses a version | +| FR51 | Cut-time successor-plan preparation plus manifest/pin/lockfile/compatibility-asset and lifecycle fixtures | default patch successor, explicitly named minor, abort before any tag or publication, immutable tag before merge-back, pre-advanced main plan, unrelated main work during equality | The plan merges before the cut version is non-reusable, survives a terminal cut with neither an immutable tag nor stable publication, skips the default patch, derives a minor from pending Changesets, splits pins/assets, or the prepared plan or merge-back lowers or duplicates an existing newer plan | +| FR52 | Promotion fixtures plus every checked-in source consumer run before and after codemod promotion | staged transform, promoted transform, missing source, ambiguous candidates, changed entry identity | Evidence hardcodes only `next/`, keeps a duplicate source, silently chooses among candidates, or passes when the promoted transform no longer exposes the same identity and entries | +| FR53 | Changeset classification review against the governing component record's accessibility evidence | a fix that replaces documented keyboard or focus behavior to meet an adopted APG pattern's required parts; a fix that changes a role, state, or accessible name to meet WCAG 2.2 A/AA; a fix that adds an optional prop | An accessibility fix is held for a minor, given a coexistence path, or classified `[breaking]`; or a change that needs an app-layer code edit, or that removes, renames, or narrows public surface, ships as an accessibility `[fix]` | +| FR54 | Changeset classification review against the surface's current record, then the same tier admission every Changeset receives | a CLI correction the CLI record classifies as `[fix]`; a CLI change the CLI record leaves undecided; a CLI change the CLI record classifies as `[breaking]` on a patch plan; a component change | A non-component change is classified by a rule this record restates instead of its surface record, a surface rule turns on risk or adoption, a surface-classified `[breaking]` change ships in a patch or a surface rule makes it patch-eligible, or a component change is classified by a surface record | +| FR59 | Pull request review of fine-pointer before-and-after evidence | a touch change that also fixes a defect fine-pointer users hit; a deliberate cross-device consistency change; a touch-only change; a new fine-pointer look proposed with touch work | A touch change alters fine-pointer behavior or appearance with neither reason, shows no fine-pointer before-and-after evidence, or ships a new fine-pointer look without its own owner decision | +| FR60 | Element counts per story at rest and open, fine and coarse pointer, against architecture INV7 | a surface component at rest; an open layer; a DOM reduction; a recorded exception | An element beyond INV7's budgets with no recorded owner exception, a hidden twin or second presentation, or a DOM reduction that removes a theme target or documented anatomy, or that is classified as breaking | ## Decision log @@ -1375,6 +1384,23 @@ reducing DOM to meet the rule is compatible. Rejected: a budget that only limits how many elements a change may add, which says nothing about what a correct component looks like. +### DEC-27 — A file path never makes a public API experimental + +**Reference:** `spec:AST-017/DEC-27` +**Decider:** `cixzhang`, `2026-10-25` + +A public module whose only experimental marker is its file path looks like any +other import to the people using it, so they come to depend on it as if it were +stable. Keeping behavior and utility modules internal, never experimental exports, +and requiring any experimental public API to show its status at the call +site where it is used, in the name the caller writes, with its own decision, +keeps the public surface honest about what is promised. The `experimental` +prop object and the `experimental_` name prefix are how that is met today. This replaces DEC-9's placement of experimental hooks, +functions, and types on a component's `/experimental` subpath; DEC-9's +`experimental` prop object stands. + +Rejected: `/experimental` subpaths that mark status by path, for modules or components. + ## Open questions None. From e11121cbe0af3013d3e3b6b06017f9a3124c1586 Mon Sep 17 00:00:00 2001 From: Cindy Zhang Date: Sun, 25 Oct 2026 08:48:16 -0700 Subject: [PATCH 2/3] spec(AST-017): FR21 scopes private modules to those without a public contract, Core marking, CLI marking owned by its records; DEC-9 points to DEC-27 --- docs/specs/AST-017/spec.md | 74 ++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/docs/specs/AST-017/spec.md b/docs/specs/AST-017/spec.md index 8433a8b33426d..474c32eae8abf 100644 --- a/docs/specs/AST-017/spec.md +++ b/docs/specs/AST-017/spec.md @@ -370,13 +370,15 @@ one removal merged ahead of the decision to allow it. canonical experimental boundary and marks it in both declarations and consumer documentation. On an existing stable component, experimental props and callbacks live inside one optional `experimental` prop object; omitting that object preserves - the component's stable defaults and behavior. Behavior and utility modules, such as - motion, gesture, and keyboard handling, are internal: they are never exported, - as experimental or otherwise. An experimental public API's status is + the component's stable defaults and behavior. A behavior or utility module without a public + contract, such as motion, gesture, or keyboard handling, stays internal: it is + never exported, as experimental or otherwise. In Core, an experimental public + API's status is visible at the call site where it is used, carried by the name the caller writes, not only in docs, declarations, or a file path; a component's `experimental` prop object meets this, and any other deliberately - experimental public API meets it with an `experimental_` name prefix. No file path or subpath, for a + experimental public API meets it with an `experimental_` name prefix. The CLI's + own records own its experimental marking (FR54). No file path or subpath, for a component or a module, conveys experimental status by itself. Declarations carry `@experimental`; authored docs carry machine-readable `stability: 'experimental'` metadata and state that the surface may change or be @@ -857,7 +859,7 @@ Public lifecycle classification remains in force with these operational effects: alongside touch work, such as a new modal backdrop dim, becomes its own design decision (FR59); - no file path or subpath marks a surface experimental and Core's behavior and - utility modules stay unexported (FR21). + utility modules without a public contract stay unexported (FR21). Main declares the next planned version, and Changeset admission uses that declaration to refuse `[breaking]` work on a patch plan. Pull-request lint does not compare the @@ -883,34 +885,34 @@ lands, maintainers apply its requirement in review and release approval. ## Verification -| Contract | Verification | Representative states | Mutation or failure expectation | -| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| FR1–FR3 | PR compatibility statement plus latest stable package inspection | released export, narrowed type, changed interaction default; unreleased and private surface; visual-only spacing, typography, layout styling, or default appearance; a new touch presentation or open-surface touch mode as a default | A change is labeled from diff size or possibility alone, a functional contract loss is hidden as visual-only, or a pixel-only change is tagged `[breaking]` even though callers need no source or configuration change, or such a touch default names no `presentation` value that restores the previous surface and mode | -| FR4–FR6 | Old-usage type/runtime/CLI regression test | alias retained, deprecation warning, broad rewrite, low-adoption caller | Contractual old usage fails despite a nonbreaking label, or risk is substituted for compatibility | -| FR7–FR8 | `pnpm check:changesets` (tag, bump, contributors, declared tier) plus FR8 migration review of `[breaking]` text | existing `[fix]`, `[feat]`, `[breaking]`, and `[experimental]` tags paired with compatible, deprecation, planned-removal, or incompatible-fix metadata | A new lifecycle replaces an established tag, tag/metadata/bump diverge, an incompatible correction uses bare `[fix]`, or migration is unusable | -| FR9–FR13 | CLI contract tests, response-schema/type snapshots, text projections, generated consumer docs, and template catalog/output tests | slug rename, metadata edit, source rebuild, optional field addition, command or schema change | Catalog data is frozen as API, a command/schema incompatibility is mislabeled as catalog-only, or a response field lacks a complete projection | -| FR12 | Minimum and representative supported-version tests plus manifest and release-note review | retained range, narrowed range, adapter, coordinated upgrade | An in-range combination breaks under a nonbreaking label, or release coordination hides the affected package or migration | -| FR14 | Help/manifest snapshots, public API and consumer docs, and focused contract tests | command, option, API/config, private rollout/test hook | Supported behavior is hidden, an environment variable changes behavior, or automation lacks a documented API | -| FR15 | Full manifest-derived command matrix, supported-consumer evidence, and scope-specific contract tests | global invariant, scoped command group, single command, programmatic API | A global control is a no-op for any command, has different meanings, or replaces a narrower owning surface | -| FR16–FR18 | Boundary inventory, hostile side-effect probes, response snapshots, and concurrent API tests | known and new extension, partial result, text/JSON/API parity, independent concurrent calls | A route bypasses the guarantee, omitted work looks complete, or one invocation changes another | -| FR19–FR20 | Proposal evidence with a regression fixture for the detection failure, plus composition and provenance tests | convention covers the case, detection fails, app plus two integrations, refusal, failing contribution, repeated load | A key ships without a reproduced detection failure, a contribution displaces the app or applies twice, a part of the effective value has no inspectable source, or a failure silently weakens protection | -| FR21–FR24 | Published-surface comparison, declaration/doc metadata checks, import-boundary checks, and Changeset classification review | experimental prop object, experimental subpath, patch evolution, promotion, unpublished adjacent field, stable warning behavior, integration theme; a component or module `/experimental` subpath; an `experimental_` export; a behavior module | A prose-only marker excludes a stable API, adjacency falsely publishes a new field, an experimental export leaks through a stable path, a patch changes stable behavior, or a promoted API remains unprotected, a file path or subpath alone marks a surface experimental, a behavior or utility module or an internal module without a stable contract is exported, or an experimental public API's call site does not show its status in the name the caller writes | -| FR25–FR27 | Stable-surface inventory and experimental-boundary matrix | package/API, CSS/token, CLI/config, persistence, build tooling, docs, whole Lab component | An unmarked experiment changes stable defaults or a stable surface bypasses compatibility review | -| FR28–FR31 | Declared-tier check: on a minor, the `[breaking]` Changeset describes the change (old usage, replacement, migration); on a patch, a breaking Changeset is refused and the deprecation ships instead; declaration/doc/build/CLI warning projections; old/new fixtures; dry-run/idempotence/migration tests; maintained-source and latest-stable upgrade evidence | whole component, prop/value, theme field/helper/token/target/source, CLI command, CLI option; old-only, new-only, both, static/dynamic/uncertain migration, text/JSON, development/production | A warning has no usable modernization route, production runtime changes for deprecation, maintained guidance teaches the old surface, a migration guesses, old behavior degrades early, cadence invents a wait, or one cleanup id covers multiple incompatible deltas | -| FR32–FR35 | Latest-stable victim fixture, pre-existing authority named in the Changeset or pull request, owner review, declared-tier check, and emergency attestation | victim-free `[fix]`, `[fix]` plus incompatible-fix metadata, coexistence, cleanup transition, critical-harm emergency | A released victim uses bare `[fix]`, a new Changeset tag replaces metadata, a correction record disagrees with its public-surface state, or urgency bypasses migration | -| FR36–FR40 | Owner-declared target, locked final-patch receipt, three-way delta classification against the merged `[breaking]` Changesets, and separated release notes | ordinary cadence, immediate pair, cleanup, release metadata, pre/post-lock compatible fix | Cadence becomes eligibility, the final patch is recut for bookkeeping, a feature or incompatible fix enters the incidental-fix lane, or release notes merge cleanup with fixes | -| FR41–FR44 | Schema validation, PR declaration, semantic stable/base/head comparisons, exact-main gate, and immutable publish/rollback receipt | duplicate ids, missing evidence, route/schema removal, old-client metadata, partial publish, safe rollback | A label passes without semantics, a delta maps zero or multiple times, fixed-group membership drifts, or a release rebuilds under one identity | -| FR45 | Docs route inventory, repository-reference checks, and Changeset review | catalog entry rename, former-route miss, stable command and JSON schema | Catalog routing is frozen as API, or a rename silently changes the contractual command or response schema | -| FR46–FR47 | Declared-tier Changeset admission, canary publication ordering, and release-versioning fixtures | owner increase/decrease, above/equal/below stable, split group, `[breaking]` on patch and minor plans | Pending Changesets derive the plan, `[breaking]` passes on a patch or fails on a minor, equality publishes a canary, or a lower/split declaration publishes instead of failing | -| FR48 | Category and classification admission under each declared tier | patch release with `[breaking]`, patch release with a deprecation, minor release with `[breaking]` | Incompatible work passes in a patch release, or a deprecation is refused in one | -| FR49 | Malformed and inconsistent declaration fixtures | disagreeing group versions, canary identifier, release version unlike the cut manifest, already-released version, skipped version, no stable tag | An untrustworthy or mismatched declaration is released | -| FR50 | Three-way squash merge-back fixtures bound to the immutable tag, branch head, and plan digest | exact branch outputs, frozen deletion, branch-only marker exclusion, post-cut Changeset/manifest edit, newer main declaration, merge conflict, regenerated output | Merge-back differs from release bytes, lands lifecycle files, consumes post-cut work, overwrites a newer main declaration or unrelated field, or chooses a version | -| FR51 | Cut-time successor-plan preparation plus manifest/pin/lockfile/compatibility-asset and lifecycle fixtures | default patch successor, explicitly named minor, abort before any tag or publication, immutable tag before merge-back, pre-advanced main plan, unrelated main work during equality | The plan merges before the cut version is non-reusable, survives a terminal cut with neither an immutable tag nor stable publication, skips the default patch, derives a minor from pending Changesets, splits pins/assets, or the prepared plan or merge-back lowers or duplicates an existing newer plan | -| FR52 | Promotion fixtures plus every checked-in source consumer run before and after codemod promotion | staged transform, promoted transform, missing source, ambiguous candidates, changed entry identity | Evidence hardcodes only `next/`, keeps a duplicate source, silently chooses among candidates, or passes when the promoted transform no longer exposes the same identity and entries | -| FR53 | Changeset classification review against the governing component record's accessibility evidence | a fix that replaces documented keyboard or focus behavior to meet an adopted APG pattern's required parts; a fix that changes a role, state, or accessible name to meet WCAG 2.2 A/AA; a fix that adds an optional prop | An accessibility fix is held for a minor, given a coexistence path, or classified `[breaking]`; or a change that needs an app-layer code edit, or that removes, renames, or narrows public surface, ships as an accessibility `[fix]` | -| FR54 | Changeset classification review against the surface's current record, then the same tier admission every Changeset receives | a CLI correction the CLI record classifies as `[fix]`; a CLI change the CLI record leaves undecided; a CLI change the CLI record classifies as `[breaking]` on a patch plan; a component change | A non-component change is classified by a rule this record restates instead of its surface record, a surface rule turns on risk or adoption, a surface-classified `[breaking]` change ships in a patch or a surface rule makes it patch-eligible, or a component change is classified by a surface record | -| FR59 | Pull request review of fine-pointer before-and-after evidence | a touch change that also fixes a defect fine-pointer users hit; a deliberate cross-device consistency change; a touch-only change; a new fine-pointer look proposed with touch work | A touch change alters fine-pointer behavior or appearance with neither reason, shows no fine-pointer before-and-after evidence, or ships a new fine-pointer look without its own owner decision | -| FR60 | Element counts per story at rest and open, fine and coarse pointer, against architecture INV7 | a surface component at rest; an open layer; a DOM reduction; a recorded exception | An element beyond INV7's budgets with no recorded owner exception, a hidden twin or second presentation, or a DOM reduction that removes a theme target or documented anatomy, or that is classified as breaking | +| Contract | Verification | Representative states | Mutation or failure expectation | +| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FR1–FR3 | PR compatibility statement plus latest stable package inspection | released export, narrowed type, changed interaction default; unreleased and private surface; visual-only spacing, typography, layout styling, or default appearance; a new touch presentation or open-surface touch mode as a default | A change is labeled from diff size or possibility alone, a functional contract loss is hidden as visual-only, or a pixel-only change is tagged `[breaking]` even though callers need no source or configuration change, or such a touch default names no `presentation` value that restores the previous surface and mode | +| FR4–FR6 | Old-usage type/runtime/CLI regression test | alias retained, deprecation warning, broad rewrite, low-adoption caller | Contractual old usage fails despite a nonbreaking label, or risk is substituted for compatibility | +| FR7–FR8 | `pnpm check:changesets` (tag, bump, contributors, declared tier) plus FR8 migration review of `[breaking]` text | existing `[fix]`, `[feat]`, `[breaking]`, and `[experimental]` tags paired with compatible, deprecation, planned-removal, or incompatible-fix metadata | A new lifecycle replaces an established tag, tag/metadata/bump diverge, an incompatible correction uses bare `[fix]`, or migration is unusable | +| FR9–FR13 | CLI contract tests, response-schema/type snapshots, text projections, generated consumer docs, and template catalog/output tests | slug rename, metadata edit, source rebuild, optional field addition, command or schema change | Catalog data is frozen as API, a command/schema incompatibility is mislabeled as catalog-only, or a response field lacks a complete projection | +| FR12 | Minimum and representative supported-version tests plus manifest and release-note review | retained range, narrowed range, adapter, coordinated upgrade | An in-range combination breaks under a nonbreaking label, or release coordination hides the affected package or migration | +| FR14 | Help/manifest snapshots, public API and consumer docs, and focused contract tests | command, option, API/config, private rollout/test hook | Supported behavior is hidden, an environment variable changes behavior, or automation lacks a documented API | +| FR15 | Full manifest-derived command matrix, supported-consumer evidence, and scope-specific contract tests | global invariant, scoped command group, single command, programmatic API | A global control is a no-op for any command, has different meanings, or replaces a narrower owning surface | +| FR16–FR18 | Boundary inventory, hostile side-effect probes, response snapshots, and concurrent API tests | known and new extension, partial result, text/JSON/API parity, independent concurrent calls | A route bypasses the guarantee, omitted work looks complete, or one invocation changes another | +| FR19–FR20 | Proposal evidence with a regression fixture for the detection failure, plus composition and provenance tests | convention covers the case, detection fails, app plus two integrations, refusal, failing contribution, repeated load | A key ships without a reproduced detection failure, a contribution displaces the app or applies twice, a part of the effective value has no inspectable source, or a failure silently weakens protection | +| FR21–FR24 | Published-surface comparison, declaration/doc metadata checks, import-boundary checks, and Changeset classification review | experimental prop object, experimental subpath, patch evolution, promotion, unpublished adjacent field, stable warning behavior, integration theme; a component or module `/experimental` subpath; an `experimental_` export; a behavior module | A prose-only marker excludes a stable API, adjacency falsely publishes a new field, an experimental export leaks through a stable path, a patch changes stable behavior, or a promoted API remains unprotected, a file path or subpath alone marks a surface experimental, a behavior, utility, or internal module without a public contract is exported, or an experimental public API's call site does not show its status in the name the caller writes | +| FR25–FR27 | Stable-surface inventory and experimental-boundary matrix | package/API, CSS/token, CLI/config, persistence, build tooling, docs, whole Lab component | An unmarked experiment changes stable defaults or a stable surface bypasses compatibility review | +| FR28–FR31 | Declared-tier check: on a minor, the `[breaking]` Changeset describes the change (old usage, replacement, migration); on a patch, a breaking Changeset is refused and the deprecation ships instead; declaration/doc/build/CLI warning projections; old/new fixtures; dry-run/idempotence/migration tests; maintained-source and latest-stable upgrade evidence | whole component, prop/value, theme field/helper/token/target/source, CLI command, CLI option; old-only, new-only, both, static/dynamic/uncertain migration, text/JSON, development/production | A warning has no usable modernization route, production runtime changes for deprecation, maintained guidance teaches the old surface, a migration guesses, old behavior degrades early, cadence invents a wait, or one cleanup id covers multiple incompatible deltas | +| FR32–FR35 | Latest-stable victim fixture, pre-existing authority named in the Changeset or pull request, owner review, declared-tier check, and emergency attestation | victim-free `[fix]`, `[fix]` plus incompatible-fix metadata, coexistence, cleanup transition, critical-harm emergency | A released victim uses bare `[fix]`, a new Changeset tag replaces metadata, a correction record disagrees with its public-surface state, or urgency bypasses migration | +| FR36–FR40 | Owner-declared target, locked final-patch receipt, three-way delta classification against the merged `[breaking]` Changesets, and separated release notes | ordinary cadence, immediate pair, cleanup, release metadata, pre/post-lock compatible fix | Cadence becomes eligibility, the final patch is recut for bookkeeping, a feature or incompatible fix enters the incidental-fix lane, or release notes merge cleanup with fixes | +| FR41–FR44 | Schema validation, PR declaration, semantic stable/base/head comparisons, exact-main gate, and immutable publish/rollback receipt | duplicate ids, missing evidence, route/schema removal, old-client metadata, partial publish, safe rollback | A label passes without semantics, a delta maps zero or multiple times, fixed-group membership drifts, or a release rebuilds under one identity | +| FR45 | Docs route inventory, repository-reference checks, and Changeset review | catalog entry rename, former-route miss, stable command and JSON schema | Catalog routing is frozen as API, or a rename silently changes the contractual command or response schema | +| FR46–FR47 | Declared-tier Changeset admission, canary publication ordering, and release-versioning fixtures | owner increase/decrease, above/equal/below stable, split group, `[breaking]` on patch and minor plans | Pending Changesets derive the plan, `[breaking]` passes on a patch or fails on a minor, equality publishes a canary, or a lower/split declaration publishes instead of failing | +| FR48 | Category and classification admission under each declared tier | patch release with `[breaking]`, patch release with a deprecation, minor release with `[breaking]` | Incompatible work passes in a patch release, or a deprecation is refused in one | +| FR49 | Malformed and inconsistent declaration fixtures | disagreeing group versions, canary identifier, release version unlike the cut manifest, already-released version, skipped version, no stable tag | An untrustworthy or mismatched declaration is released | +| FR50 | Three-way squash merge-back fixtures bound to the immutable tag, branch head, and plan digest | exact branch outputs, frozen deletion, branch-only marker exclusion, post-cut Changeset/manifest edit, newer main declaration, merge conflict, regenerated output | Merge-back differs from release bytes, lands lifecycle files, consumes post-cut work, overwrites a newer main declaration or unrelated field, or chooses a version | +| FR51 | Cut-time successor-plan preparation plus manifest/pin/lockfile/compatibility-asset and lifecycle fixtures | default patch successor, explicitly named minor, abort before any tag or publication, immutable tag before merge-back, pre-advanced main plan, unrelated main work during equality | The plan merges before the cut version is non-reusable, survives a terminal cut with neither an immutable tag nor stable publication, skips the default patch, derives a minor from pending Changesets, splits pins/assets, or the prepared plan or merge-back lowers or duplicates an existing newer plan | +| FR52 | Promotion fixtures plus every checked-in source consumer run before and after codemod promotion | staged transform, promoted transform, missing source, ambiguous candidates, changed entry identity | Evidence hardcodes only `next/`, keeps a duplicate source, silently chooses among candidates, or passes when the promoted transform no longer exposes the same identity and entries | +| FR53 | Changeset classification review against the governing component record's accessibility evidence | a fix that replaces documented keyboard or focus behavior to meet an adopted APG pattern's required parts; a fix that changes a role, state, or accessible name to meet WCAG 2.2 A/AA; a fix that adds an optional prop | An accessibility fix is held for a minor, given a coexistence path, or classified `[breaking]`; or a change that needs an app-layer code edit, or that removes, renames, or narrows public surface, ships as an accessibility `[fix]` | +| FR54 | Changeset classification review against the surface's current record, then the same tier admission every Changeset receives | a CLI correction the CLI record classifies as `[fix]`; a CLI change the CLI record leaves undecided; a CLI change the CLI record classifies as `[breaking]` on a patch plan; a component change | A non-component change is classified by a rule this record restates instead of its surface record, a surface rule turns on risk or adoption, a surface-classified `[breaking]` change ships in a patch or a surface rule makes it patch-eligible, or a component change is classified by a surface record | +| FR59 | Pull request review of fine-pointer before-and-after evidence | a touch change that also fixes a defect fine-pointer users hit; a deliberate cross-device consistency change; a touch-only change; a new fine-pointer look proposed with touch work | A touch change alters fine-pointer behavior or appearance with neither reason, shows no fine-pointer before-and-after evidence, or ships a new fine-pointer look without its own owner decision | +| FR60 | Element counts per story at rest and open, fine and coarse pointer, against architecture INV7 | a surface component at rest; an open layer; a DOM reduction; a recorded exception | An element beyond INV7's budgets with no recorded owner exception, a hidden twin or second presentation, or a DOM reduction that removes a theme target or documented anatomy, or that is classified as breaking | ## Decision log @@ -1080,7 +1082,8 @@ Keep Lab as the canary-only home for an entire experimental component. When an existing stable component needs to test a new prop or callback, put it inside the component's optional `experimental` prop object so every callsite opts in visibly and the ordinary prop namespace stays stable. Put experimental hooks, functions, -and types on the owning component's `/experimental` subpath. Mark the same surface +and types on the owning component's `/experimental` subpath (DEC-27 replaces +this placement). Mark the same surface in declarations and authored docs, and keep stable defaults unchanged when the experimental boundary is unused. @@ -1391,7 +1394,8 @@ Rejected: a budget that only limits how many elements a change may add, which sa A public module whose only experimental marker is its file path looks like any other import to the people using it, so they come to depend on it as if it were -stable. Keeping behavior and utility modules internal, never experimental exports, +stable. Keeping behavior and utility modules without a public contract internal, +never experimental exports, and requiring any experimental public API to show its status at the call site where it is used, in the name the caller writes, with its own decision, keeps the public surface honest about what is promised. The `experimental` From 4f6fb067300bae62bdc51c5504714ab6df3210eb Mon Sep 17 00:00:00 2001 From: Cindy Zhang Date: Sun, 25 Oct 2026 09:18:36 -0700 Subject: [PATCH 3/3] spec(AST-017): DEC-27, the own-decision sentence, and the verification row share FR21's Core scope --- docs/specs/AST-017/spec.md | 64 +++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/docs/specs/AST-017/spec.md b/docs/specs/AST-017/spec.md index 474c32eae8abf..124f77744b46d 100644 --- a/docs/specs/AST-017/spec.md +++ b/docs/specs/AST-017/spec.md @@ -383,8 +383,8 @@ one removal merged ahead of the decision to allow it. carry `@experimental`; authored docs carry machine-readable `stability: 'experimental'` metadata and state that the surface may change or be removed in a patch release. Prose-only warnings and reviewer knowledge do not - establish this boundary. Any public experimental API also carries its own - specification decision. + establish this boundary. Any public experimental API in Core also carries + its own specification decision. - **FR22 — Experimental changes are patch-level and stay confined.** Adding, changing, renaming, or removing an explicitly experimental surface uses an `[experimental]` Changeset and a patch bump. An incompatibility confined to that @@ -885,34 +885,34 @@ lands, maintainers apply its requirement in review and release approval. ## Verification -| Contract | Verification | Representative states | Mutation or failure expectation | -| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| FR1–FR3 | PR compatibility statement plus latest stable package inspection | released export, narrowed type, changed interaction default; unreleased and private surface; visual-only spacing, typography, layout styling, or default appearance; a new touch presentation or open-surface touch mode as a default | A change is labeled from diff size or possibility alone, a functional contract loss is hidden as visual-only, or a pixel-only change is tagged `[breaking]` even though callers need no source or configuration change, or such a touch default names no `presentation` value that restores the previous surface and mode | -| FR4–FR6 | Old-usage type/runtime/CLI regression test | alias retained, deprecation warning, broad rewrite, low-adoption caller | Contractual old usage fails despite a nonbreaking label, or risk is substituted for compatibility | -| FR7–FR8 | `pnpm check:changesets` (tag, bump, contributors, declared tier) plus FR8 migration review of `[breaking]` text | existing `[fix]`, `[feat]`, `[breaking]`, and `[experimental]` tags paired with compatible, deprecation, planned-removal, or incompatible-fix metadata | A new lifecycle replaces an established tag, tag/metadata/bump diverge, an incompatible correction uses bare `[fix]`, or migration is unusable | -| FR9–FR13 | CLI contract tests, response-schema/type snapshots, text projections, generated consumer docs, and template catalog/output tests | slug rename, metadata edit, source rebuild, optional field addition, command or schema change | Catalog data is frozen as API, a command/schema incompatibility is mislabeled as catalog-only, or a response field lacks a complete projection | -| FR12 | Minimum and representative supported-version tests plus manifest and release-note review | retained range, narrowed range, adapter, coordinated upgrade | An in-range combination breaks under a nonbreaking label, or release coordination hides the affected package or migration | -| FR14 | Help/manifest snapshots, public API and consumer docs, and focused contract tests | command, option, API/config, private rollout/test hook | Supported behavior is hidden, an environment variable changes behavior, or automation lacks a documented API | -| FR15 | Full manifest-derived command matrix, supported-consumer evidence, and scope-specific contract tests | global invariant, scoped command group, single command, programmatic API | A global control is a no-op for any command, has different meanings, or replaces a narrower owning surface | -| FR16–FR18 | Boundary inventory, hostile side-effect probes, response snapshots, and concurrent API tests | known and new extension, partial result, text/JSON/API parity, independent concurrent calls | A route bypasses the guarantee, omitted work looks complete, or one invocation changes another | -| FR19–FR20 | Proposal evidence with a regression fixture for the detection failure, plus composition and provenance tests | convention covers the case, detection fails, app plus two integrations, refusal, failing contribution, repeated load | A key ships without a reproduced detection failure, a contribution displaces the app or applies twice, a part of the effective value has no inspectable source, or a failure silently weakens protection | -| FR21–FR24 | Published-surface comparison, declaration/doc metadata checks, import-boundary checks, and Changeset classification review | experimental prop object, experimental subpath, patch evolution, promotion, unpublished adjacent field, stable warning behavior, integration theme; a component or module `/experimental` subpath; an `experimental_` export; a behavior module | A prose-only marker excludes a stable API, adjacency falsely publishes a new field, an experimental export leaks through a stable path, a patch changes stable behavior, or a promoted API remains unprotected, a file path or subpath alone marks a surface experimental, a behavior, utility, or internal module without a public contract is exported, or an experimental public API's call site does not show its status in the name the caller writes | -| FR25–FR27 | Stable-surface inventory and experimental-boundary matrix | package/API, CSS/token, CLI/config, persistence, build tooling, docs, whole Lab component | An unmarked experiment changes stable defaults or a stable surface bypasses compatibility review | -| FR28–FR31 | Declared-tier check: on a minor, the `[breaking]` Changeset describes the change (old usage, replacement, migration); on a patch, a breaking Changeset is refused and the deprecation ships instead; declaration/doc/build/CLI warning projections; old/new fixtures; dry-run/idempotence/migration tests; maintained-source and latest-stable upgrade evidence | whole component, prop/value, theme field/helper/token/target/source, CLI command, CLI option; old-only, new-only, both, static/dynamic/uncertain migration, text/JSON, development/production | A warning has no usable modernization route, production runtime changes for deprecation, maintained guidance teaches the old surface, a migration guesses, old behavior degrades early, cadence invents a wait, or one cleanup id covers multiple incompatible deltas | -| FR32–FR35 | Latest-stable victim fixture, pre-existing authority named in the Changeset or pull request, owner review, declared-tier check, and emergency attestation | victim-free `[fix]`, `[fix]` plus incompatible-fix metadata, coexistence, cleanup transition, critical-harm emergency | A released victim uses bare `[fix]`, a new Changeset tag replaces metadata, a correction record disagrees with its public-surface state, or urgency bypasses migration | -| FR36–FR40 | Owner-declared target, locked final-patch receipt, three-way delta classification against the merged `[breaking]` Changesets, and separated release notes | ordinary cadence, immediate pair, cleanup, release metadata, pre/post-lock compatible fix | Cadence becomes eligibility, the final patch is recut for bookkeeping, a feature or incompatible fix enters the incidental-fix lane, or release notes merge cleanup with fixes | -| FR41–FR44 | Schema validation, PR declaration, semantic stable/base/head comparisons, exact-main gate, and immutable publish/rollback receipt | duplicate ids, missing evidence, route/schema removal, old-client metadata, partial publish, safe rollback | A label passes without semantics, a delta maps zero or multiple times, fixed-group membership drifts, or a release rebuilds under one identity | -| FR45 | Docs route inventory, repository-reference checks, and Changeset review | catalog entry rename, former-route miss, stable command and JSON schema | Catalog routing is frozen as API, or a rename silently changes the contractual command or response schema | -| FR46–FR47 | Declared-tier Changeset admission, canary publication ordering, and release-versioning fixtures | owner increase/decrease, above/equal/below stable, split group, `[breaking]` on patch and minor plans | Pending Changesets derive the plan, `[breaking]` passes on a patch or fails on a minor, equality publishes a canary, or a lower/split declaration publishes instead of failing | -| FR48 | Category and classification admission under each declared tier | patch release with `[breaking]`, patch release with a deprecation, minor release with `[breaking]` | Incompatible work passes in a patch release, or a deprecation is refused in one | -| FR49 | Malformed and inconsistent declaration fixtures | disagreeing group versions, canary identifier, release version unlike the cut manifest, already-released version, skipped version, no stable tag | An untrustworthy or mismatched declaration is released | -| FR50 | Three-way squash merge-back fixtures bound to the immutable tag, branch head, and plan digest | exact branch outputs, frozen deletion, branch-only marker exclusion, post-cut Changeset/manifest edit, newer main declaration, merge conflict, regenerated output | Merge-back differs from release bytes, lands lifecycle files, consumes post-cut work, overwrites a newer main declaration or unrelated field, or chooses a version | -| FR51 | Cut-time successor-plan preparation plus manifest/pin/lockfile/compatibility-asset and lifecycle fixtures | default patch successor, explicitly named minor, abort before any tag or publication, immutable tag before merge-back, pre-advanced main plan, unrelated main work during equality | The plan merges before the cut version is non-reusable, survives a terminal cut with neither an immutable tag nor stable publication, skips the default patch, derives a minor from pending Changesets, splits pins/assets, or the prepared plan or merge-back lowers or duplicates an existing newer plan | -| FR52 | Promotion fixtures plus every checked-in source consumer run before and after codemod promotion | staged transform, promoted transform, missing source, ambiguous candidates, changed entry identity | Evidence hardcodes only `next/`, keeps a duplicate source, silently chooses among candidates, or passes when the promoted transform no longer exposes the same identity and entries | -| FR53 | Changeset classification review against the governing component record's accessibility evidence | a fix that replaces documented keyboard or focus behavior to meet an adopted APG pattern's required parts; a fix that changes a role, state, or accessible name to meet WCAG 2.2 A/AA; a fix that adds an optional prop | An accessibility fix is held for a minor, given a coexistence path, or classified `[breaking]`; or a change that needs an app-layer code edit, or that removes, renames, or narrows public surface, ships as an accessibility `[fix]` | -| FR54 | Changeset classification review against the surface's current record, then the same tier admission every Changeset receives | a CLI correction the CLI record classifies as `[fix]`; a CLI change the CLI record leaves undecided; a CLI change the CLI record classifies as `[breaking]` on a patch plan; a component change | A non-component change is classified by a rule this record restates instead of its surface record, a surface rule turns on risk or adoption, a surface-classified `[breaking]` change ships in a patch or a surface rule makes it patch-eligible, or a component change is classified by a surface record | -| FR59 | Pull request review of fine-pointer before-and-after evidence | a touch change that also fixes a defect fine-pointer users hit; a deliberate cross-device consistency change; a touch-only change; a new fine-pointer look proposed with touch work | A touch change alters fine-pointer behavior or appearance with neither reason, shows no fine-pointer before-and-after evidence, or ships a new fine-pointer look without its own owner decision | -| FR60 | Element counts per story at rest and open, fine and coarse pointer, against architecture INV7 | a surface component at rest; an open layer; a DOM reduction; a recorded exception | An element beyond INV7's budgets with no recorded owner exception, a hidden twin or second presentation, or a DOM reduction that removes a theme target or documented anatomy, or that is classified as breaking | +| Contract | Verification | Representative states | Mutation or failure expectation | +| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| FR1–FR3 | PR compatibility statement plus latest stable package inspection | released export, narrowed type, changed interaction default; unreleased and private surface; visual-only spacing, typography, layout styling, or default appearance; a new touch presentation or open-surface touch mode as a default | A change is labeled from diff size or possibility alone, a functional contract loss is hidden as visual-only, or a pixel-only change is tagged `[breaking]` even though callers need no source or configuration change, or such a touch default names no `presentation` value that restores the previous surface and mode | +| FR4–FR6 | Old-usage type/runtime/CLI regression test | alias retained, deprecation warning, broad rewrite, low-adoption caller | Contractual old usage fails despite a nonbreaking label, or risk is substituted for compatibility | +| FR7–FR8 | `pnpm check:changesets` (tag, bump, contributors, declared tier) plus FR8 migration review of `[breaking]` text | existing `[fix]`, `[feat]`, `[breaking]`, and `[experimental]` tags paired with compatible, deprecation, planned-removal, or incompatible-fix metadata | A new lifecycle replaces an established tag, tag/metadata/bump diverge, an incompatible correction uses bare `[fix]`, or migration is unusable | +| FR9–FR13 | CLI contract tests, response-schema/type snapshots, text projections, generated consumer docs, and template catalog/output tests | slug rename, metadata edit, source rebuild, optional field addition, command or schema change | Catalog data is frozen as API, a command/schema incompatibility is mislabeled as catalog-only, or a response field lacks a complete projection | +| FR12 | Minimum and representative supported-version tests plus manifest and release-note review | retained range, narrowed range, adapter, coordinated upgrade | An in-range combination breaks under a nonbreaking label, or release coordination hides the affected package or migration | +| FR14 | Help/manifest snapshots, public API and consumer docs, and focused contract tests | command, option, API/config, private rollout/test hook | Supported behavior is hidden, an environment variable changes behavior, or automation lacks a documented API | +| FR15 | Full manifest-derived command matrix, supported-consumer evidence, and scope-specific contract tests | global invariant, scoped command group, single command, programmatic API | A global control is a no-op for any command, has different meanings, or replaces a narrower owning surface | +| FR16–FR18 | Boundary inventory, hostile side-effect probes, response snapshots, and concurrent API tests | known and new extension, partial result, text/JSON/API parity, independent concurrent calls | A route bypasses the guarantee, omitted work looks complete, or one invocation changes another | +| FR19–FR20 | Proposal evidence with a regression fixture for the detection failure, plus composition and provenance tests | convention covers the case, detection fails, app plus two integrations, refusal, failing contribution, repeated load | A key ships without a reproduced detection failure, a contribution displaces the app or applies twice, a part of the effective value has no inspectable source, or a failure silently weakens protection | +| FR21–FR24 | Published-surface comparison, declaration/doc metadata checks, import-boundary checks, and Changeset classification review | experimental prop object, experimental subpath, patch evolution, promotion, unpublished adjacent field, stable warning behavior, integration theme; a component or module `/experimental` subpath; an `experimental_` export; a behavior module | A prose-only marker excludes a stable API, adjacency falsely publishes a new field, an experimental export leaks through a stable path, a patch changes stable behavior, or a promoted API remains unprotected, a file path or subpath alone marks a surface experimental, a behavior, utility, or internal module without a public contract is exported, or an experimental public API in Core does not show its status at the call site in the name the caller writes | +| FR25–FR27 | Stable-surface inventory and experimental-boundary matrix | package/API, CSS/token, CLI/config, persistence, build tooling, docs, whole Lab component | An unmarked experiment changes stable defaults or a stable surface bypasses compatibility review | +| FR28–FR31 | Declared-tier check: on a minor, the `[breaking]` Changeset describes the change (old usage, replacement, migration); on a patch, a breaking Changeset is refused and the deprecation ships instead; declaration/doc/build/CLI warning projections; old/new fixtures; dry-run/idempotence/migration tests; maintained-source and latest-stable upgrade evidence | whole component, prop/value, theme field/helper/token/target/source, CLI command, CLI option; old-only, new-only, both, static/dynamic/uncertain migration, text/JSON, development/production | A warning has no usable modernization route, production runtime changes for deprecation, maintained guidance teaches the old surface, a migration guesses, old behavior degrades early, cadence invents a wait, or one cleanup id covers multiple incompatible deltas | +| FR32–FR35 | Latest-stable victim fixture, pre-existing authority named in the Changeset or pull request, owner review, declared-tier check, and emergency attestation | victim-free `[fix]`, `[fix]` plus incompatible-fix metadata, coexistence, cleanup transition, critical-harm emergency | A released victim uses bare `[fix]`, a new Changeset tag replaces metadata, a correction record disagrees with its public-surface state, or urgency bypasses migration | +| FR36–FR40 | Owner-declared target, locked final-patch receipt, three-way delta classification against the merged `[breaking]` Changesets, and separated release notes | ordinary cadence, immediate pair, cleanup, release metadata, pre/post-lock compatible fix | Cadence becomes eligibility, the final patch is recut for bookkeeping, a feature or incompatible fix enters the incidental-fix lane, or release notes merge cleanup with fixes | +| FR41–FR44 | Schema validation, PR declaration, semantic stable/base/head comparisons, exact-main gate, and immutable publish/rollback receipt | duplicate ids, missing evidence, route/schema removal, old-client metadata, partial publish, safe rollback | A label passes without semantics, a delta maps zero or multiple times, fixed-group membership drifts, or a release rebuilds under one identity | +| FR45 | Docs route inventory, repository-reference checks, and Changeset review | catalog entry rename, former-route miss, stable command and JSON schema | Catalog routing is frozen as API, or a rename silently changes the contractual command or response schema | +| FR46–FR47 | Declared-tier Changeset admission, canary publication ordering, and release-versioning fixtures | owner increase/decrease, above/equal/below stable, split group, `[breaking]` on patch and minor plans | Pending Changesets derive the plan, `[breaking]` passes on a patch or fails on a minor, equality publishes a canary, or a lower/split declaration publishes instead of failing | +| FR48 | Category and classification admission under each declared tier | patch release with `[breaking]`, patch release with a deprecation, minor release with `[breaking]` | Incompatible work passes in a patch release, or a deprecation is refused in one | +| FR49 | Malformed and inconsistent declaration fixtures | disagreeing group versions, canary identifier, release version unlike the cut manifest, already-released version, skipped version, no stable tag | An untrustworthy or mismatched declaration is released | +| FR50 | Three-way squash merge-back fixtures bound to the immutable tag, branch head, and plan digest | exact branch outputs, frozen deletion, branch-only marker exclusion, post-cut Changeset/manifest edit, newer main declaration, merge conflict, regenerated output | Merge-back differs from release bytes, lands lifecycle files, consumes post-cut work, overwrites a newer main declaration or unrelated field, or chooses a version | +| FR51 | Cut-time successor-plan preparation plus manifest/pin/lockfile/compatibility-asset and lifecycle fixtures | default patch successor, explicitly named minor, abort before any tag or publication, immutable tag before merge-back, pre-advanced main plan, unrelated main work during equality | The plan merges before the cut version is non-reusable, survives a terminal cut with neither an immutable tag nor stable publication, skips the default patch, derives a minor from pending Changesets, splits pins/assets, or the prepared plan or merge-back lowers or duplicates an existing newer plan | +| FR52 | Promotion fixtures plus every checked-in source consumer run before and after codemod promotion | staged transform, promoted transform, missing source, ambiguous candidates, changed entry identity | Evidence hardcodes only `next/`, keeps a duplicate source, silently chooses among candidates, or passes when the promoted transform no longer exposes the same identity and entries | +| FR53 | Changeset classification review against the governing component record's accessibility evidence | a fix that replaces documented keyboard or focus behavior to meet an adopted APG pattern's required parts; a fix that changes a role, state, or accessible name to meet WCAG 2.2 A/AA; a fix that adds an optional prop | An accessibility fix is held for a minor, given a coexistence path, or classified `[breaking]`; or a change that needs an app-layer code edit, or that removes, renames, or narrows public surface, ships as an accessibility `[fix]` | +| FR54 | Changeset classification review against the surface's current record, then the same tier admission every Changeset receives | a CLI correction the CLI record classifies as `[fix]`; a CLI change the CLI record leaves undecided; a CLI change the CLI record classifies as `[breaking]` on a patch plan; a component change | A non-component change is classified by a rule this record restates instead of its surface record, a surface rule turns on risk or adoption, a surface-classified `[breaking]` change ships in a patch or a surface rule makes it patch-eligible, or a component change is classified by a surface record | +| FR59 | Pull request review of fine-pointer before-and-after evidence | a touch change that also fixes a defect fine-pointer users hit; a deliberate cross-device consistency change; a touch-only change; a new fine-pointer look proposed with touch work | A touch change alters fine-pointer behavior or appearance with neither reason, shows no fine-pointer before-and-after evidence, or ships a new fine-pointer look without its own owner decision | +| FR60 | Element counts per story at rest and open, fine and coarse pointer, against architecture INV7 | a surface component at rest; an open layer; a DOM reduction; a recorded exception | An element beyond INV7's budgets with no recorded owner exception, a hidden twin or second presentation, or a DOM reduction that removes a theme target or documented anatomy, or that is classified as breaking | ## Decision log @@ -1396,8 +1396,8 @@ A public module whose only experimental marker is its file path looks like any other import to the people using it, so they come to depend on it as if it were stable. Keeping behavior and utility modules without a public contract internal, never experimental exports, -and requiring any experimental public API to show its status at the call -site where it is used, in the name the caller writes, with its own decision, +and requiring any experimental public API in Core to show its status at the +call site where it is used, in the name the caller writes, with its own decision, keeps the public surface honest about what is promised. The `experimental` prop object and the `experimental_` name prefix are how that is met today. This replaces DEC-9's placement of experimental hooks, functions, and types on a component's `/experimental` subpath; DEC-9's