@3fn/core 13.0.0 → 14.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. package/.kiro/agents/ada-prompt.md +80 -132
  2. package/.kiro/agents/ada-prompt.md.attribution.json +45 -0
  3. package/.kiro/agents/ada.json +44 -59
  4. package/.kiro/agents/ada.json.attribution.json +13 -0
  5. package/.kiro/agents/data-prompt.md +83 -74
  6. package/.kiro/agents/data-prompt.md.attribution.json +53 -0
  7. package/.kiro/agents/data.json +31 -38
  8. package/.kiro/agents/data.json.attribution.json +13 -0
  9. package/.kiro/agents/kenya-prompt.md +83 -72
  10. package/.kiro/agents/kenya-prompt.md.attribution.json +53 -0
  11. package/.kiro/agents/kenya.json +27 -35
  12. package/.kiro/agents/kenya.json.attribution.json +13 -0
  13. package/.kiro/agents/leonardo-prompt.md +176 -234
  14. package/.kiro/agents/leonardo-prompt.md.attribution.json +45 -0
  15. package/.kiro/agents/leonardo.json +28 -36
  16. package/.kiro/agents/leonardo.json.attribution.json +13 -0
  17. package/.kiro/agents/lina-prompt.md +110 -151
  18. package/.kiro/agents/lina-prompt.md.attribution.json +53 -0
  19. package/.kiro/agents/lina.json +46 -59
  20. package/.kiro/agents/lina.json.attribution.json +13 -0
  21. package/.kiro/agents/sparky-prompt.md +89 -72
  22. package/.kiro/agents/sparky-prompt.md.attribution.json +53 -0
  23. package/.kiro/agents/sparky.json +37 -37
  24. package/.kiro/agents/sparky.json.attribution.json +13 -0
  25. package/.kiro/agents/stacy-prompt.md +74 -48
  26. package/.kiro/agents/stacy-prompt.md.attribution.json +45 -0
  27. package/.kiro/agents/stacy.json +28 -30
  28. package/.kiro/agents/stacy.json.attribution.json +13 -0
  29. package/.kiro/agents/thurgood-prompt.md +94 -138
  30. package/.kiro/agents/thurgood-prompt.md.attribution.json +45 -0
  31. package/.kiro/agents/thurgood.json +31 -35
  32. package/.kiro/agents/thurgood.json.attribution.json +13 -0
  33. package/.kiro/steering/AI-Collaboration-Principles.md +3 -3
  34. package/.kiro/steering/Civitas-System-Overview.md +7 -16
  35. package/.kiro/steering/DesignerPunk-Systems-Overview.md +6 -6
  36. package/.kiro/steering/Spec-Feedback-Protocol.md +2 -11
  37. package/.kiro/steering/Task-Completion-Protocol.md +98 -17
  38. package/.kiro/steering/core-goals.md +3 -3
  39. package/.kiro/steering/personal-note.md +1 -1
  40. package/.kiro/steering/start-up-tasks.md +17 -6
  41. package/application-mcp-server/src/index.ts +26 -0
  42. package/dist/ComponentTokens.android.kt +12 -12
  43. package/dist/ComponentTokens.ios.swift +12 -12
  44. package/dist/ComponentTokens.web.css +3 -3
  45. package/dist/DesignTokens.android.kt +1 -1
  46. package/dist/DesignTokens.dtcg.json +8 -5
  47. package/dist/DesignTokens.figma.json +2 -2
  48. package/dist/DesignTokens.ios.swift +1 -1
  49. package/dist/DesignTokens.web.css +1 -1
  50. package/dist/android/DesignTokens.android.kt +1 -1
  51. package/dist/blend/OklchBlendCalculator.js +1 -0
  52. package/dist/blend/ThemeAwareBlendUtilities.web.d.ts +13 -2
  53. package/dist/blend/ThemeAwareBlendUtilities.web.js +6 -1
  54. package/dist/browser/designerpunk.esm.js +36 -90
  55. package/dist/browser/designerpunk.esm.min.js +33 -36
  56. package/dist/browser/designerpunk.umd.js +36 -90
  57. package/dist/browser/designerpunk.umd.min.js +47 -50
  58. package/dist/browser/tokens.css +3 -3
  59. package/dist/build/tokens/defineComponentTokens.d.ts +10 -0
  60. package/dist/build/tokens/defineComponentTokens.js +26 -0
  61. package/dist/components/core/Avatar-Base/avatar.tokens.d.ts +21 -26
  62. package/dist/components/core/Avatar-Base/avatar.tokens.js +31 -34
  63. package/dist/components/core/Avatar-Base/index.d.ts +1 -1
  64. package/dist/components/core/Avatar-Base/index.js +2 -2
  65. package/dist/components/core/Avatar-Base/platforms/web/Avatar.web.js +24 -5
  66. package/dist/components/core/Button-CTA/examples/BasicUsage.d.ts +16 -28
  67. package/dist/components/core/Button-CTA/examples/BasicUsage.js +18 -43
  68. package/dist/components/core/Button-CTA/platforms/web/ButtonCTA.web.d.ts +3 -15
  69. package/dist/components/core/Button-CTA/platforms/web/ButtonCTA.web.js +9 -58
  70. package/dist/components/core/Button-CTA/types.d.ts +0 -24
  71. package/dist/components/core/Button-CTA/types.js +6 -0
  72. package/dist/components/core/Button-Icon/buttonIcon.tokens.d.ts +28 -14
  73. package/dist/components/core/Button-Icon/buttonIcon.tokens.js +35 -20
  74. package/dist/components/core/Input-Text-Base/types.d.ts +13 -1
  75. package/dist/components/core/Input-Text-Password/platforms/web/InputTextPassword.web.js +11 -2
  76. package/dist/generators/DTCGFormatGenerator.js +8 -0
  77. package/dist/generators/TokenFileGenerator.js +7 -2
  78. package/dist/integration/BuildErrorHandler.js +2 -2
  79. package/dist/ios/DesignTokens.ios.swift +1 -1
  80. package/dist/mcp/application-mcp.js +24 -0
  81. package/dist/mcp/docs-mcp.js +130 -15
  82. package/dist/mcp/product-mcp.js +25 -0
  83. package/dist/tokens/OpacityTokens.js +1 -1
  84. package/dist/tokens/component/progress.d.ts +65 -5
  85. package/dist/tokens/component/progress.js +79 -18
  86. package/dist/tokens/semantic/BlendTokens.d.ts +10 -3
  87. package/dist/tokens/semantic/BlendTokens.js +17 -5
  88. package/dist/tokens/semantic/OpacityTokens.d.ts +4 -4
  89. package/dist/tokens/semantic/OpacityTokens.js +4 -4
  90. package/dist/types/ComponentTypes.d.ts +1 -1
  91. package/dist/types/generated/TokenTypes.d.ts +1 -1
  92. package/dist/types/generated/TokenTypes.js +1 -1
  93. package/dist/validators/StemmaTokenUsageValidator.js +3 -2
  94. package/dist/web/DesignTokens.web.css +1 -1
  95. package/governance/BUILD-SYSTEM-SETUP.md +1 -2
  96. package/governance/Component-Development-Guide.md +23 -13
  97. package/governance/Component-Development-Standards.md +21 -20
  98. package/governance/Component-Family-Avatar.md +6 -7
  99. package/governance/Component-Family-Badge.md +19 -20
  100. package/governance/Component-Family-Button.md +30 -43
  101. package/governance/Component-Family-Chip.md +14 -15
  102. package/governance/Component-Family-Container.md +12 -13
  103. package/governance/Component-Family-Data-Display.md +1 -2
  104. package/governance/Component-Family-Divider.md +1 -2
  105. package/governance/Component-Family-Form-Inputs.md +65 -65
  106. package/governance/Component-Family-Icon.md +9 -10
  107. package/governance/Component-Family-Loading.md +1 -2
  108. package/governance/Component-Family-Modal.md +1 -2
  109. package/governance/Component-Family-Navigation.md +1 -2
  110. package/governance/Component-Family-Progress.md +0 -1
  111. package/governance/Component-Inheritance-Structures.md +219 -99
  112. package/governance/Component-MCP-Document-Template.md +6 -5
  113. package/governance/Component-Primitive-vs-Semantic-Philosophy.md +1 -1
  114. package/governance/Component-Quick-Reference.md +33 -33
  115. package/governance/Component-Readiness-Status.md +59 -44
  116. package/governance/Component-Templates.md +57 -61
  117. package/governance/Contract-System-Reference.md +7 -7
  118. package/governance/MCP-Integration-Guide.md +1 -1
  119. package/governance/Process-Cross-Reference-Standards.md +31 -13
  120. package/governance/Process-Development-Workflow.md +49 -59
  121. package/governance/Process-File-Organization.md +25 -28
  122. package/governance/Process-Hook-Operations.md +22 -11
  123. package/governance/Process-Orchestration-Model-Selection.md +92 -0
  124. package/governance/Process-Spec-Planning.md +98 -52
  125. package/governance/Process-Task-Type-Definitions.md +80 -4
  126. package/governance/Product-Handoff-Protocol.md +2 -0
  127. package/governance/Rosetta-System-Architecture.md +13 -11
  128. package/governance/Test-Behavioral-Contract-Validation.md +38 -31
  129. package/governance/Test-Failure-Audit-Methodology.md +1 -1
  130. package/governance/Token-Family-Accessibility.md +1 -2
  131. package/governance/Token-Family-Blend.md +18 -16
  132. package/governance/Token-Family-Blur.md +0 -1
  133. package/governance/Token-Family-Border.md +1 -2
  134. package/governance/Token-Family-Color.md +0 -1
  135. package/governance/Token-Family-Glow.md +1 -2
  136. package/governance/Token-Family-Layering.md +0 -1
  137. package/governance/Token-Family-Motion.md +1 -2
  138. package/governance/Token-Family-Opacity.md +0 -1
  139. package/governance/Token-Family-Radius.md +1 -2
  140. package/governance/Token-Family-Responsive.md +1 -2
  141. package/governance/Token-Family-Shadow.md +1 -2
  142. package/governance/Token-Family-Sizing.md +0 -1
  143. package/governance/Token-Family-Spacing.md +1 -2
  144. package/governance/Token-Family-Typography.md +1 -2
  145. package/governance/Token-Governance.md +8 -8
  146. package/governance/Token-Quick-Reference.md +47 -34
  147. package/governance/Token-Resolution-Patterns.md +1 -1
  148. package/governance/Token-Semantic-Structure.md +1 -1
  149. package/governance/Web-Authoring-Standards.md +5 -5
  150. package/governance/browser-distribution-guide.md +1 -4
  151. package/governance/classification-map.md +474 -0
  152. package/governance/completion-documentation-guide.md +23 -37
  153. package/governance/component-meta-authoring-guide.md +1 -1
  154. package/governance/cross-platform-vs-platform-specific-decision-framework.md +1 -1
  155. package/governance/platform-implementation-guidelines.md +2 -3
  156. package/governance/release-management-system.md +28 -63
  157. package/governance/rosetta-system-principles.md +8 -6
  158. package/governance/stemma-system-principles.md +18 -17
  159. package/mcp-server/src/index.ts +24 -6
  160. package/mcp-server/src/indexer/DocumentIndexer.ts +119 -9
  161. package/mcp-server/src/indexer/__tests__/bare-id-crossrefs.test.ts +250 -0
  162. package/mcp-server/src/indexer/cross-ref-parser.ts +29 -1
  163. package/mcp-server/src/indexer/index-health.ts +27 -2
  164. package/mcp-server/src/query/__tests__/find-docs-calibration.test.ts +11 -26
  165. package/mcp-server/src/relocation-integrity-gate/__tests__/relocation-integrity-gate.test.ts +72 -5
  166. package/mcp-server/src/relocation-integrity-gate/relocation-integrity-gate.ts +81 -24
  167. package/mcp-server/src/tools/list-cross-references.ts +2 -2
  168. package/package.json +24 -26
  169. package/src/__tests__/browser-distribution/css-bundling.test.ts +6 -4
  170. package/src/__tests__/console-allowlist.json +14 -0
  171. package/src/__tests__/console-fail-setup.ts +169 -0
  172. package/src/__tests__/integration/Spec107-DesignLanguageContext.test.ts +16 -0
  173. package/src/__tests__/stemma-system/behavioral-contract-validation.test.ts +70 -17
  174. package/src/__tests__/stemma-system/contract-catalog-name-validation.test.ts +28 -0
  175. package/src/__tests__/stemma-system/form-inputs-contracts.test.ts +223 -16
  176. package/src/__tests__/stemma-system/input-text-native-base-call-alignment.test.ts +298 -0
  177. package/src/blend/OklchBlendCalculator.ts +3 -0
  178. package/src/blend/ThemeAwareBlendUtilities.android.kt +3 -0
  179. package/src/blend/ThemeAwareBlendUtilities.ios.swift +3 -0
  180. package/src/blend/ThemeAwareBlendUtilities.web.ts +9 -1
  181. package/src/blend/__tests__/InteractionStateAudit.test.ts +12 -9
  182. package/src/build/errors/__tests__/ErrorHandler.integration.test.ts +8 -0
  183. package/src/build/errors/__tests__/ErrorHandler.test.ts +5 -0
  184. package/src/build/tokens/__tests__/defineComponentTokens.test.ts +113 -0
  185. package/src/build/tokens/defineComponentTokens.ts +43 -1
  186. package/src/build/workflow/__tests__/CICDIntegration.test.ts +12 -1
  187. package/src/cli/__tests__/init.test.ts +45 -11
  188. package/src/components/core/Avatar-Base/Avatar-Base.schema.yaml +1 -1
  189. package/src/components/core/Avatar-Base/__tests__/Avatar.accessibility.test.ts +121 -7
  190. package/src/components/core/Avatar-Base/__tests__/Avatar.image.test.ts +3 -0
  191. package/src/components/core/Avatar-Base/__tests__/Avatar.test.ts +15 -6
  192. package/src/components/core/Avatar-Base/avatar.tokens.ts +31 -34
  193. package/src/components/core/Avatar-Base/contracts.yaml +11 -1
  194. package/src/components/core/Avatar-Base/index.ts +1 -1
  195. package/src/components/core/Avatar-Base/platforms/web/Avatar.web.ts +24 -5
  196. package/src/components/core/Badge-Count-Base/contracts.yaml +1 -1
  197. package/src/components/core/Badge-Label-Base/contracts.yaml +1 -1
  198. package/src/components/core/Button-CTA/Button-CTA.schema.yaml +2 -12
  199. package/src/components/core/Button-CTA/README.md +3 -6
  200. package/src/components/core/Button-CTA/__tests__/ButtonCTA.test.ts +35 -89
  201. package/src/components/core/Button-CTA/__tests__/setup.test.ts +0 -2
  202. package/src/components/core/Button-CTA/__tests__/test-utils.ts +0 -2
  203. package/src/components/core/Button-CTA/contracts.yaml +6 -29
  204. package/src/components/core/Button-CTA/examples/BasicUsage.html +2 -14
  205. package/src/components/core/Button-CTA/examples/BasicUsage.tsx +17 -44
  206. package/src/components/core/Button-CTA/platforms/android/ButtonCTA.android.kt +12 -20
  207. package/src/components/core/Button-CTA/platforms/ios/ButtonCTA.ios.swift +12 -51
  208. package/src/components/core/Button-CTA/platforms/web/ButtonCTA.web.css +2 -26
  209. package/src/components/core/Button-CTA/platforms/web/ButtonCTA.web.ts +18 -71
  210. package/src/components/core/Button-CTA/types.ts +10 -28
  211. package/src/components/core/Button-Icon/buttonIcon.tokens.ts +43 -27
  212. package/src/components/core/Chip-Base/__tests__/ChipBase.test.ts +13 -0
  213. package/src/components/core/Chip-Filter/__tests__/ChipFilter.test.ts +13 -0
  214. package/src/components/core/Chip-Input/__tests__/ChipInput.test.ts +13 -0
  215. package/src/components/core/Input-Text-Base/Input-Text-Base.schema.yaml +30 -2
  216. package/src/components/core/Input-Text-Base/README.md +25 -2
  217. package/src/components/core/Input-Text-Base/__tests__/focusIndicators.test.ts +16 -15
  218. package/src/components/core/Input-Text-Base/contracts.yaml +90 -0
  219. package/src/components/core/Input-Text-Base/platforms/android/InputTextBase.android.kt +26 -12
  220. package/src/components/core/Input-Text-Base/platforms/ios/InputTextBase.ios.swift +195 -59
  221. package/src/components/core/Input-Text-Base/types.ts +13 -1
  222. package/src/components/core/Input-Text-Email/Input-Text-Email.schema.yaml +5 -1
  223. package/src/components/core/Input-Text-Email/README.md +8 -7
  224. package/src/components/core/Input-Text-Email/platforms/android/InputTextEmail.android.kt +1 -4
  225. package/src/components/core/Input-Text-Email/platforms/ios/InputTextEmail.ios.swift +2 -16
  226. package/src/components/core/Input-Text-Password/Input-Text-Password.schema.yaml +10 -3
  227. package/src/components/core/Input-Text-Password/README.md +9 -8
  228. package/src/components/core/Input-Text-Password/contracts.yaml +5 -0
  229. package/src/components/core/Input-Text-Password/platforms/android/InputTextPassword.android.kt +17 -7
  230. package/src/components/core/Input-Text-Password/platforms/ios/InputTextPassword.ios.swift +22 -20
  231. package/src/components/core/Input-Text-Password/platforms/web/InputTextPassword.web.ts +11 -2
  232. package/src/components/core/Input-Text-PhoneNumber/Input-Text-PhoneNumber.schema.yaml +5 -1
  233. package/src/components/core/Input-Text-PhoneNumber/README.md +9 -8
  234. package/src/components/core/Input-Text-PhoneNumber/platforms/android/InputTextPhoneNumber.android.kt +2 -5
  235. package/src/components/core/Input-Text-PhoneNumber/platforms/ios/InputTextPhoneNumber.ios.swift +3 -17
  236. package/src/components/core/Nav-Header-App/contracts.yaml +1 -1
  237. package/src/components/core/Nav-SegmentedChoice-Base/contracts.yaml +1 -1
  238. package/src/components/core/Progress-Indicator-Connector-Base/contracts.yaml +1 -1
  239. package/src/components/core/Progress-Indicator-Label-Base/contracts.yaml +1 -1
  240. package/src/components/core/Progress-Indicator-Node-Base/contracts.yaml +1 -1
  241. package/src/components/core/Progress-Stepper-Base/__tests__/StepperBase.test.ts +5 -2
  242. package/src/components/core/Progress-Stepper-Detailed/__tests__/StepperDetailed.test.ts +5 -2
  243. package/src/generators/DTCGFormatGenerator.ts +6 -0
  244. package/src/generators/TokenFileGenerator.ts +7 -2
  245. package/src/generators/__tests__/DTCGConfigOptions.test.ts +14 -5
  246. package/src/integration/BuildErrorHandler.ts +2 -2
  247. package/src/tokens/OpacityTokens.ts +1 -1
  248. package/src/tokens/__tests__/OpacityTokens.test.ts +3 -1
  249. package/src/tokens/__tests__/ProgressTokenCompliance.test.ts +5 -3
  250. package/src/tokens/__tests__/ProgressTokenFormulas.test.ts +11 -11
  251. package/src/tokens/__tests__/ProgressTokenTranslation.test.ts +22 -20
  252. package/src/tokens/component/progress.ts +83 -21
  253. package/src/tokens/semantic/BlendTokens.ts +26 -5
  254. package/src/tokens/semantic/OpacityTokens.ts +4 -4
  255. package/src/types/ComponentTypes.ts +1 -1
  256. package/src/types/generated/TokenTypes.ts +1 -1
  257. package/src/validators/StemmaTokenUsageValidator.ts +3 -2
  258. package/token-index/components.yaml +8 -8
  259. package/token-index/semantics.yaml +1 -2
  260. package/src/tools/release/__tests__/ChangeClassifier.test.ts +0 -133
  261. package/src/tools/release/__tests__/ChangeExtractor.test.ts +0 -222
  262. package/src/tools/release/__tests__/GitHubPublisher.test.ts +0 -240
  263. package/src/tools/release/__tests__/NotesRenderer.test.ts +0 -142
  264. package/src/tools/release/__tests__/NpmPublisher.test.ts +0 -289
  265. package/src/tools/release/__tests__/PipelineIntegration.test.ts +0 -188
  266. package/src/tools/release/__tests__/ReleasePipeline.test.ts +0 -192
  267. package/src/tools/release/__tests__/SemanticVersionValidator.test.ts +0 -49
  268. package/src/tools/release/__tests__/SummaryScanner.test.ts +0 -141
  269. package/src/tools/release/__tests__/TagResolver.test.ts +0 -91
  270. package/src/tools/release/__tests__/VersionCalculator.test.ts +0 -270
  271. package/src/tools/release/__tests__/helpers/NpmMockHelper.ts +0 -80
  272. package/src/tools/release/cli/ReleasePipeline.ts +0 -165
  273. package/src/tools/release/cli/release-tool.ts +0 -107
  274. package/src/tools/release/pipeline/ChangeClassifier.ts +0 -61
  275. package/src/tools/release/pipeline/ChangeExtractor.ts +0 -87
  276. package/src/tools/release/pipeline/NotesRenderer.ts +0 -66
  277. package/src/tools/release/pipeline/SummaryScanner.ts +0 -70
  278. package/src/tools/release/pipeline/TagResolver.ts +0 -40
  279. package/src/tools/release/pipeline/VersionCalculator.ts +0 -375
  280. package/src/tools/release/publishers/GitHubPublisher.ts +0 -228
  281. package/src/tools/release/publishers/NpmPublisher.ts +0 -196
  282. package/src/tools/release/release-config.json +0 -5
  283. package/src/tools/release/types/index.ts +0 -282
  284. package/src/tools/release/validators/SemanticVersionValidator.ts +0 -67
@@ -323,33 +323,83 @@ describe('Behavioral Contract Validation Suite', () => {
323
323
  });
324
324
 
325
325
  describe('WCAG Reference Validation', () => {
326
+ /**
327
+ * NORMATIVE WCAG-REQUIRED MATCHER — Spec 125-B U2, Task 4.2 (audit) → 4.3 (arm).
328
+ *
329
+ * Copied VERBATIM from the Task 4.2 audit matcher:
330
+ * .kiro/specs/125-B-classification-map/completion/u2/wcag-required-matcher.ts
331
+ *
332
+ * CONTINUITY CONTRACT (Req 12.3 / LINA tasks-R1): Task 4.2's pre-arm WCAG audit
333
+ * enumerated the corpus THROUGH this exact function (and the same COMPONENTS
334
+ * loader below), so "audit-clean ⇒ arm-green by construction" holds ONLY while
335
+ * this predicate stays unmodified. ANY change to it RE-OPENS the 4.2 audit —
336
+ * do not "improve" it here.
337
+ *
338
+ * Replaces the legacy DORMANT six-name trigger (armed-but-aimed-at-6-retired
339
+ * legacy contract names — Spec 063 renamed them to canonical
340
+ * `{category}_{concept}` form; this check never matched the renamed names).
341
+ */
342
+ const WCAG_REQUIRED_EXACT: ReadonlySet<string> = new Set([
343
+ 'interaction_focusable',
344
+ 'interaction_focus_ring',
345
+ 'state_disabled',
346
+ 'state_error',
347
+ ]);
348
+
349
+ function isWcagRequiredContract(contractName: string): boolean {
350
+ if (WCAG_REQUIRED_EXACT.has(contractName)) return true;
351
+ if (contractName.startsWith('accessibility_')) return true;
352
+ if (contractName.startsWith('content_') && contractName.endsWith('_label')) return true;
353
+ return false;
354
+ }
355
+
326
356
  it('accessibility-related contracts should have WCAG references', () => {
327
- const accessibilityContracts = [
328
- 'focusable',
329
- 'focus_ring',
330
- 'disabled_state',
331
- 'error_state_display',
332
- 'reduced_motion_support',
333
- 'accessibility_hidden',
334
- ];
335
-
357
+ // Match-count floor (Req 12.4 / DD3): the DORMANT lesson made structural —
358
+ // an empty (or stale) selection fails the check itself.
359
+ let totalSelected = 0;
360
+ const perLiteralCounts: Record<string, number> = {
361
+ interaction_focusable: 0,
362
+ interaction_focus_ring: 0,
363
+ state_error: 0,
364
+ };
365
+
336
366
  for (const component of COMPONENTS) {
337
367
  const contracts = loadComponentContracts(component);
338
-
368
+
339
369
  if (!contracts) continue;
340
-
370
+
341
371
  for (const [contractName, contract] of Object.entries(contracts)) {
342
- if (accessibilityContracts.includes(contractName)) {
372
+ if (isWcagRequiredContract(contractName)) {
373
+ totalSelected++;
374
+ if (contractName in perLiteralCounts) {
375
+ perLiteralCounts[contractName]++;
376
+ }
377
+
343
378
  const hasWcag = contract.wcag && contract.wcag.length > 0;
344
-
379
+
345
380
  if (!hasWcag) {
346
381
  console.log(`${component}.${contractName}: Missing WCAG reference`);
347
382
  }
348
-
383
+
349
384
  expect(hasWcag).toBe(true);
350
385
  }
351
386
  }
352
387
  }
388
+
389
+ // Aggregate floor (DD3): non-empty selection (audited at 69 on the current
390
+ // corpus — Task 4.2 adjudication table).
391
+ expect(totalSelected).toBeGreaterThan(0);
392
+
393
+ // Per-literal presence floor (DD3), THREE literals per Peter's 2026-07-14
394
+ // amendment — NOT the four DD3 originally recorded. `state_disabled` is
395
+ // EXCLUDED from this floor pending the Button-CTA disabled-state
396
+ // adjudication (the matcher itself is unchanged: state_disabled contracts
397
+ // are still selected above and still must carry a valid wcag ref; only the
398
+ // per-literal PRESENCE floor omits it). See:
399
+ // .kiro/specs/125-B-classification-map/completion/u2/stemma-pre-arm-adjudication.md § 7
400
+ expect(perLiteralCounts.interaction_focusable).toBeGreaterThan(0);
401
+ expect(perLiteralCounts.interaction_focus_ring).toBeGreaterThan(0);
402
+ expect(perLiteralCounts.state_error).toBeGreaterThan(0);
353
403
  });
354
404
 
355
405
  it('WCAG references should follow standard format', () => {
@@ -430,9 +480,12 @@ describe('Behavioral Contract Validation Suite', () => {
430
480
  console.log(`\nValidation Criteria Summary:`);
431
481
  console.log(` With validation: ${contractsWithValidation}`);
432
482
  console.log(` Without validation: ${contractsWithoutValidation}`);
433
-
434
- // Most contracts should have validation criteria
435
- expect(contractsWithValidation).toBeGreaterThan(0);
483
+
484
+ // Promoted (Req 12.6 / DD4, audit-first — Task 4.2's inventory found the
485
+ // corpus already clean: 234 non-inherited contracts, 0 without validation;
486
+ // zero fixes, zero escalations needed). Inherited-contract skip preserved
487
+ // above. DD4: no exemption mechanism — fix-all, escalate-if-candidate.
488
+ expect(contractsWithoutValidation).toBe(0);
436
489
  });
437
490
 
438
491
  it('validation criteria should include testable assertions', () => {
@@ -26,6 +26,19 @@ const CATALOG_PATH = path.join(process.cwd(), 'governance/Contract-System-Refere
26
26
  const BASELINE_CONCEPT_COUNT = 116;
27
27
  const EXPECTED_CATEGORY_COUNT = 10;
28
28
 
29
+ /**
30
+ * Contracts authored ahead of Concept Catalog ratification (ballot pending).
31
+ *
32
+ * Each entry MUST reference the ruling that authorized authoring the contract
33
+ * before the concept ballot ratified. This allowlist is SELF-CLEANING: once
34
+ * the concept lands in the catalog (ballot RATIFIED), the validation below
35
+ * fails with a removal instruction — a stale entry cannot linger.
36
+ */
37
+ const PENDING_BALLOT_CONCEPTS: Record<string, string> = {
38
+ // (empty — state_readonly ratified and landed in the catalog, 2026-07-15;
39
+ // add entries here only for contracts whose concept ballot is still pending)
40
+ };
41
+
29
42
  /**
30
43
  * Parse the Concept Catalog from Contract-System-Reference.md.
31
44
  *
@@ -145,6 +158,21 @@ describe('Contract Catalog Name Validation', () => {
145
158
  );
146
159
  }
147
160
 
161
+ // Pending-ballot carve-out (self-cleaning): a contract authored ahead
162
+ // of catalog ratification passes only while the concept is absent
163
+ // from the catalog. The moment the ballot ratifies and the concept
164
+ // lands in the catalog, the stale allowlist entry fails the test.
165
+ if (contractKey in PENDING_BALLOT_CONCEPTS) {
166
+ if (catalogCategory.has(concept)) {
167
+ throw new Error(
168
+ `Contract '${contractKey}' is now in the Concept Catalog — its ballot has ` +
169
+ `ratified. Remove it from PENDING_BALLOT_CONCEPTS. ` +
170
+ `(Entry: ${PENDING_BALLOT_CONCEPTS[contractKey]})`
171
+ );
172
+ }
173
+ return; // valid category + documented pending ballot
174
+ }
175
+
148
176
  if (!catalogCategory.has(concept)) {
149
177
  throw new Error(
150
178
  `Component ${componentName}: contract '${contractKey}' has unrecognized concept '${concept}' in category '${category}'`
@@ -93,22 +93,8 @@ const CONTRACT_PATTERNS: Record<string, Record<string, RegExp[]>> = {
93
93
  /errorMessage/g,
94
94
  ],
95
95
  },
96
- disabled_state: {
97
- web: [
98
- /disabled/gi,
99
- /aria-disabled/g,
100
- /cursor:\s*not-allowed/g,
101
- ],
102
- ios: [
103
- /disabled/gi,
104
- /isDisabled/g,
105
- ],
106
- android: [
107
- /enabled/gi,
108
- /isEnabled/g,
109
- /disabled/gi,
110
- ],
111
- },
96
+ // NOTE: disabled_state is a standardized EXCLUSION (no-disabled-states philosophy).
97
+ // Implementation-level guard patterns live in DISABLED_EXCLUSION_GUARD_PATTERNS below.
112
98
  focus_ring: {
113
99
  web: [
114
100
  /:focus-visible/g,
@@ -141,6 +127,22 @@ const CONTRACT_PATTERNS: Record<string, Record<string, RegExp[]>> = {
141
127
  },
142
128
  };
143
129
 
130
+ // Disabled-state exclusion guard (no-disabled-states philosophy, Spec 066 / Button-CTA pattern).
131
+ // These patterns must NOT appear in any Form Inputs platform implementation.
132
+ // The iOS `.disabled(` pattern was added per the iOS readOnly adjudication
133
+ // (.kiro/issues/input-text-base-ios-readonly-adjudication.md — RULED B-prime,
134
+ // Peter 2026-07-15, condition 5): iOS previously implemented readOnly via
135
+ // SwiftUI .disabled(readOnly), which the original patterns missed — that is
136
+ // how a "dimmed" readOnly field survived two disabled-cleanup passes. There
137
+ // are zero legitimate `.disabled(` uses in Form Inputs platform sources.
138
+ const DISABLED_EXCLUSION_GUARD_PATTERNS: RegExp[] = [
139
+ /isDisabled/,
140
+ /disabledBlend/,
141
+ /aria-disabled/,
142
+ /cursor:\s*not-allowed/,
143
+ /\.disabled\(/, // SwiftUI disabled modifier — disabled semantics regardless of intent
144
+ ];
145
+
144
146
  /**
145
147
  * Load component schema
146
148
  */
@@ -371,6 +373,211 @@ describe('Form Inputs Family Behavioral Contracts', () => {
371
373
  expect(content).toContain('state_disabled');
372
374
  expect(content).toContain('excludes');
373
375
  });
376
+
377
+ it('no platform implementation should contain disabled-state handling', () => {
378
+ // Exclusion guard (Spec 066 / Button-CTA pattern): the contracts.yaml
379
+ // exclusion must hold in code. readOnly is the documented alternative
380
+ // and must never be implemented via disabled semantics — iOS renders
381
+ // readOnly as selectable Text in the field chrome (B-prime ruling,
382
+ // 2026-07-15), never via .disabled(readOnly).
383
+ for (const formComponent of FORM_INPUT_COMPONENTS) {
384
+ for (const platform of ['web', 'ios', 'android']) {
385
+ const content = loadPlatformImpl(formComponent, platform);
386
+ if (!content) continue;
387
+
388
+ // Raw-source matching (strict): banned patterns may not appear
389
+ // anywhere in a platform implementation, comments included —
390
+ // reference the mechanism descriptively in docs, never literally.
391
+ const violations = DISABLED_EXCLUSION_GUARD_PATTERNS
392
+ .filter(pattern => pattern.test(content))
393
+ .map(pattern => pattern.source);
394
+
395
+ if (violations.length > 0) {
396
+ console.log(`${formComponent} (${platform}) disabled-state handling found: ${violations.join(', ')}`);
397
+ }
398
+
399
+ expect(violations).toEqual([]);
400
+ }
401
+ }
402
+ });
403
+ });
404
+
405
+ describe('state_readonly contract (iOS readOnly adjudication, RULED B-prime 2026-07-15)', () => {
406
+ // @see .kiro/issues/input-text-base-ios-readonly-adjudication.md
407
+ // These are the first executable readOnly assertions anywhere in the
408
+ // component (pre-ruling coverage was zero on all platforms).
409
+
410
+ it('Input-Text-Base contracts.yaml declares state_readonly', () => {
411
+ const contracts = loadContracts('Input-Text-Base');
412
+ expect(contracts).not.toBeNull();
413
+ expect(contracts!.state_readonly).toBeDefined();
414
+ expect(contracts!.state_readonly.category).toBe('state');
415
+ expect(contracts!.state_readonly.required).toBe(true);
416
+ expect(contracts!.state_readonly.platforms).toEqual(['web', 'ios', 'android']);
417
+ // The contract must pin "never disabled semantics" — the invariant
418
+ // the original implementation violated.
419
+ expect(contracts!.state_readonly.behavior).toMatch(/disabled/i);
420
+ });
421
+
422
+ it('Input-Text-Base interaction_focusable declares the iOS readOnly carve-out', () => {
423
+ const contractsPath = path.join(COMPONENTS_DIR, 'Input-Text-Base', 'contracts.yaml');
424
+ const content = fs.readFileSync(contractsPath, 'utf-8');
425
+ const parsed = yaml.load(content) as Record<string, any>;
426
+ const focusable = parsed.contracts.interaction_focusable;
427
+
428
+ // Declared, never silent: iOS carve-out + affirmative web/Android
429
+ // focus-order participation (adjudication condition 1).
430
+ expect(focusable.behavior).toMatch(/iOS readOnly carve-out/i);
431
+ expect(focusable.behavior).toMatch(/mitigable declared exception/i);
432
+ expect(focusable.behavior).toMatch(/REMAIN in\s+focus order/);
433
+ });
434
+
435
+ it('Input-Text-Password contracts.yaml excludes state_readonly (security fork deleted)', () => {
436
+ const contractsPath = path.join(COMPONENTS_DIR, 'Input-Text-Password', 'contracts.yaml');
437
+ const content = fs.readFileSync(contractsPath, 'utf-8');
438
+ const parsed = yaml.load(content) as Record<string, any>;
439
+
440
+ expect(parsed.excludes).toBeDefined();
441
+ expect(parsed.excludes.state_readonly).toBeDefined();
442
+ expect(parsed.excludes.state_readonly.reason).toMatch(/security/i);
443
+ expect(parsed.excludes.state_readonly.reference).toMatch(
444
+ /input-text-base-ios-readonly-adjudication/
445
+ );
446
+ });
447
+
448
+ it('no Input-Text iOS implementation contains .disabled( anywhere', () => {
449
+ for (const formComponent of FORM_INPUT_COMPONENTS) {
450
+ const content = loadPlatformImpl(formComponent, 'ios');
451
+ if (!content) continue;
452
+
453
+ // Raw-source assertion, comments included: this is how
454
+ // .disabled(readOnly) survived two disabled-cleanup passes.
455
+ expect(content).not.toMatch(/\.disabled\(/);
456
+ }
457
+ });
458
+
459
+ it('iOS readOnly path renders selectable Text inside the shared field chrome', () => {
460
+ const content = loadPlatformImpl('Input-Text-Base', 'ios');
461
+ expect(content).not.toBeNull();
462
+
463
+ // B-prime: Text(value).textSelection(.enabled) wrapped by the
464
+ // extracted chrome modifier, shared with the editable path.
465
+ expect(content).toMatch(/\.textSelection\(\.enabled\)/);
466
+ expect(content).toMatch(/InputTextBaseFieldChrome/);
467
+ // The chrome must reserve min-height from the typographyInput
468
+ // line-height token unconditionally on both paths (Kenya hardening).
469
+ expect(content).toMatch(/typographyInput\.lineHeight/);
470
+ expect(content).toMatch(/minHeight/);
471
+ });
472
+
473
+ it('iOS readOnly Text path is unreachable for secure fields (SECURITY gate)', () => {
474
+ const content = loadPlatformImpl('Input-Text-Base', 'ios');
475
+ expect(content).not.toBeNull();
476
+
477
+ // The gate: the read-only display path must exclude .password so a
478
+ // secure value can never render as selectable plaintext Text.
479
+ expect(content).toMatch(/readOnly\s*&&\s*type\s*!=\s*\.password/);
480
+ // And the rendering branch must key off the gated property, not the
481
+ // raw readOnly prop.
482
+ expect(content).toMatch(/if\s+isReadOnlyDisplay/);
483
+ });
484
+
485
+ it('iOS Input-Text-Password never forwards readOnly to the base', () => {
486
+ const content = loadPlatformImpl('Input-Text-Password', 'ios');
487
+ expect(content).not.toBeNull();
488
+
489
+ // Both branches (masked SecureField and revealed .text) must strip
490
+ // readOnly — the revealed branch would otherwise route a secret to
491
+ // the base's plaintext Text path.
492
+ const forwarded = content!.match(/readOnly:\s*readOnly/g);
493
+ expect(forwarded).toBeNull();
494
+
495
+ const stripped = content!.match(/readOnly:\s*false/g);
496
+ expect(stripped).not.toBeNull();
497
+ expect(stripped!.length).toBeGreaterThanOrEqual(2);
498
+ });
499
+
500
+ it('iOS composes a read-only accessibility indication (never a dimmed trait)', () => {
501
+ const content = loadPlatformImpl('Input-Text-Base', 'ios');
502
+ expect(content).not.toBeNull();
503
+
504
+ // Composed label + value + read-only hint (Kenya R1: iOS has no
505
+ // read-only trait; the hint is the announcement route).
506
+ expect(content).toMatch(/Read only/);
507
+ expect(content).toMatch(/accessibilityHint/);
508
+ });
509
+
510
+ it('web implements readOnly via the native readonly attribute', () => {
511
+ const content = loadPlatformImpl('Input-Text-Base', 'web');
512
+ expect(content).not.toBeNull();
513
+
514
+ // Native readonly attribute = native semantics (implicit
515
+ // aria-readonly), focus-order participation, selectable content.
516
+ expect(content).toMatch(/readOnly\s*\?\s*'readonly'/);
517
+ });
518
+
519
+ it('Android passes readOnly through to the text field without disabled semantics', () => {
520
+ const content = loadPlatformImpl('Input-Text-Base', 'android');
521
+ expect(content).not.toBeNull();
522
+
523
+ // Compose readOnly keeps the field focusable/selectable/copyable.
524
+ expect(content).toMatch(/readOnly\s*=\s*readOnly/);
525
+ // Never enabled=false — the only Compose mechanism that would remove
526
+ // a readOnly field from focus order is banned disabled semantics.
527
+ expect(content).not.toMatch(/enabled\s*=\s*false/);
528
+ });
529
+
530
+ it('Android sets TalkBack stateDescription("read only") conditionally on readOnly', () => {
531
+ const content = loadPlatformImpl('Input-Text-Base', 'android');
532
+ expect(content).not.toBeNull();
533
+
534
+ // Announcement parity (adjudication condition 3): explicit
535
+ // stateDescription in the semantics block, gated on readOnly —
536
+ // never announced on editable fields, never a disabled/dimmed state.
537
+ expect(content).toMatch(
538
+ /if\s*\(readOnly\)\s*\{[^}]*stateDescription\s*=\s*"read only"/
539
+ );
540
+ // And the semantics property must actually be wired up (import), not
541
+ // just mentioned in a comment.
542
+ expect(content).toMatch(/import\s+androidx\.compose\.ui\.semantics\.stateDescription/);
543
+ });
544
+
545
+ it('Android Input-Text-Password never forwards readOnly to the base', () => {
546
+ const content = loadPlatformImpl('Input-Text-Password', 'android');
547
+ expect(content).not.toBeNull();
548
+
549
+ // readOnly is contracted out of the Password variant (adjudication
550
+ // condition 4). On Android this is contract conformance, not a
551
+ // security fix — the masked PasswordVisualTransformation already
552
+ // suppresses copy/cut ([DATA R2] §3) — but the exclusion must hold
553
+ // uniformly: the prop is accepted for API stability and stripped.
554
+ const forwarded = content!.match(/readOnly\s*=\s*readOnly/g);
555
+ expect(forwarded).toBeNull();
556
+
557
+ const stripped = content!.match(/readOnly\s*=\s*false/g);
558
+ expect(stripped).not.toBeNull();
559
+ expect(stripped!.length).toBeGreaterThanOrEqual(1);
560
+ });
561
+
562
+ it('web Input-Text-Password never forwards read-only to the base', () => {
563
+ const content = loadPlatformImpl('Input-Text-Password', 'web');
564
+ expect(content).not.toBeNull();
565
+
566
+ // readOnly is contracted out of the Password variant (adjudication
567
+ // condition 4). On web this is contract conformance, not a security
568
+ // fix — a readonly password input stays masked and browsers block
569
+ // copy from type=password — but the exclusion holds uniformly on all
570
+ // three platforms.
571
+ // The render template must not interpolate read-only onto the inner
572
+ // input-text-base, and no code path may read the attribute.
573
+ expect(content).not.toMatch(/\$\{readOnly/);
574
+ expect(content).not.toMatch(/hasAttribute\(\s*['"]read-only['"]\s*\)/);
575
+
576
+ // The attribute stays accepted (observedAttributes) for API
577
+ // stability, documented as ignored with the adjudication reference.
578
+ expect(content).toMatch(/'read-only',/);
579
+ expect(content).toMatch(/input-text-base-ios-readonly-adjudication/);
580
+ });
374
581
  });
375
582
  });
376
583