From 3b30fdab0248668ad9ba08535c1fedf940684e20 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Jul 2026 10:15:41 +0000 Subject: [PATCH 1/3] docs: add ADR-0034 requiring a scope on feat and fix Record the decision that commit-lint requires a scope on the two version-driving types (feat, fix): the release tooling partitions commits into trains by scope, so an unscoped feat/fix is silently dropped from the release notes and the changelog. The ADR weighs the alternatives (keep documenting only, require a scope on every type, change the release tooling), is drafted bilingually, and is indexed. Refs: #293 --- ...-on-the-version-driving-commit-types.fr.md | 75 +++++++++++++++++++ ...ope-on-the-version-driving-commit-types.md | 75 +++++++++++++++++++ doc/handwritten/for-maintainers/adr/README.md | 1 + 3 files changed, 151 insertions(+) create mode 100644 doc/handwritten/for-maintainers/adr/0034-require-a-scope-on-the-version-driving-commit-types.fr.md create mode 100644 doc/handwritten/for-maintainers/adr/0034-require-a-scope-on-the-version-driving-commit-types.md diff --git a/doc/handwritten/for-maintainers/adr/0034-require-a-scope-on-the-version-driving-commit-types.fr.md b/doc/handwritten/for-maintainers/adr/0034-require-a-scope-on-the-version-driving-commit-types.fr.md new file mode 100644 index 00000000..3ecde3bd --- /dev/null +++ b/doc/handwritten/for-maintainers/adr/0034-require-a-scope-on-the-version-driving-commit-types.fr.md @@ -0,0 +1,75 @@ +# ADR-0034 | Exiger un scope sur les types de commit qui pilotent la version + +🌍 🇬🇧 [English](0034-require-a-scope-on-the-version-driving-commit-types.md) · 🇫🇷 Français (ce fichier) + +**Statut :** Accepté +**Date :** 2026-07-23 +**Décideurs :** Reefact + +## Contexte + +Le dépôt publie plusieurs paquets versionnés indépendamment, regroupés en **trains de release** ; `tools/trains.sh` est la source de vérité unique qui associe chaque scope de Conventional Commit à un train (`lib`, `cli`, `dum`). Les release notes GitHub et le changelog d'un train sont générés (`tools/packaging/release-notes.sh`, `tools/changelog/collect-prs.sh`) en sélectionnant les commits dont le scope appartient à ce train. Un commit sans scope n'appartient à aucun train et est écarté. Cet écart est voulu : les commits d'infrastructure (`ci`, `chore`, `build`, `docs`) ne portent pas de scope et n'ont pas à figurer dans le dossier de release d'un paquet. + +La convention de commit (CONTRIBUTING.md) rend le scope syntaxiquement optionnel sur tous les types. Les deux types **qui pilotent la version** sont `feat` (pilote `MINOR`) et `fix` (pilote `PATCH`) ; par définition, ils dénotent un changement visible par le consommateur d'un paquet. `commit-lint` — un unique script partagé par le hook local `commit-msg` et la vérification CI — valide la forme de l'en-tête et, jusqu'ici, acceptait un `feat`/`fix` sans scope. + +La conséquence a déjà été observée et documentée (issue #231, PR #292) : un contributeur qui suit la convention à la lettre peut écrire un `feat:`/`fix:` visible par l'utilisateur et sans scope, qui disparaît alors silencieusement à la fois des release notes et du changelog. Le changement est livré dans les binaires mais n'apparaît jamais dans le dossier de release, sans erreur à aucun moment. + +## Décision + +`commit-lint` exige un scope de Conventional Commit sur les deux types qui pilotent la version, `feat` et `fix`, et rejette un commit sans scope — de façon identique au hook `commit-msg` et à la vérification CI — tandis que tout autre type conserve le scope optionnel. + +## Justification + +Le dossier de release est partitionné uniquement par le scope ; sur les types qui pilotent la version, le scope n'est donc pas une aide à la lisibilité mais la clé de routage qui décide si un changement visible par le consommateur est enregistré, ou non. Un `feat`/`fix` sans scope n'est pas simplement moins lisible — il est retiré du compte rendu public de ce qui a été livré, en silence. + +La documentation seule ne peut pas combler ce manque. La règle déjà énoncée dans CONTRIBUTING dépend du fait que l'auteur se souvienne d'une conséquence invisible au moment d'écrire le commit ; l'échec ne produit aucun signal et ne se révèle que plus tard, comme un trou qu'un utilisateur remarque. Porter la règle dans `commit-lint` transforme une convention à mémoriser en un invariant appliqué, attrapé à l'écriture et — pour un hook local contourné — de nouveau en CI, la même application en couches sur laquelle la convention s'appuie déjà pour ses autres règles. + +Confiner l'exigence à `feat` et `fix`, c'est ce qui la garde correcte plutôt que seulement stricte : seuls ces deux types pilotent une version et dénotent un changement visible par le consommateur. Les types qui ne pilotent pas la version sont précisément le travail d'infrastructure et à l'échelle du dépôt qui n'appartient légitimement à aucun train ; y exiger un scope serait du bruit et présenterait à tort du travail de dépôt comme du travail de composant. + +Le coût — rejeter certains messages valides aujourd'hui — est borné et auto-correcteur : l'auteur ajoute un scope dès que le hook ou la CI le signale, guidé par un message qui nomme la conséquence. La grammaire de l'en-tête et le câblage partagé hook/CI relèvent de l'implémentation, dans `tools/commit-lint/lint-commit-message.sh` et le workflow commit-lint — pas ici. + +## Alternatives envisagées + +### Continuer à documenter la règle sans l'appliquer (l'état après #231) + +Envisagé parce que #231 / #292 ont déjà corrigé la documentation, de sorte que la convention énonce désormais l'exigence en prose. Rejeté parce qu'une règle en prose contre un échec silencieux et invisible repose sur le fait que l'auteur s'en souvienne précisément quand rien ne signale l'erreur ; le dossier de release peut toujours perdre un changement visible par l'utilisateur, ce que la règle existe pour empêcher. + +### Exiger un scope sur tous les types + +Envisagé parce qu'une règle unique et uniforme « toujours un scope » est la plus simple à énoncer et à implémenter. Rejeté parce que les types qui ne pilotent pas la version sont le cas légitime « sans scope » — infrastructure, documentation à l'échelle du dépôt, ADR, exemples de `FirstClassErrors.Usage` — et y forcer un scope serait du bruit et attribuerait à tort du travail de dépôt à un composant et à son train. + +### Modifier l'outillage de release pour conserver les feat/fix sans scope + +Envisagé parce que l'écart se produit dans `release-notes.sh` / `collect-prs.sh`, qui pourraient au contraire router un commit qui pilote la version et sans scope vers un train par défaut. Rejeté parce qu'il n'existe pas de train par défaut correct pour un changement visible par le consommateur et sans scope : le scope est précisément ce qui identifie quel paquet a changé, donc deviner classerait le changement dans le dossier de release du mauvais paquet — une erreur plus subtile et plus difficile à remarquer que refuser le commit ambigu à la source. + +## Conséquences + +### Positives + +* Un `feat`/`fix` visible par l'utilisateur ne peut plus disparaître des release notes ni du changelog ; le dossier de release est complet par construction. +* La règle que la convention documente déjà devient appliquée — à l'écriture et en CI — comblant le manque que #231 ne pouvait que décrire. +* Le travail d'infrastructure et à l'échelle du dépôt reste sans scope, de sorte que l'application n'ajoute aucune friction là où un scope n'aurait pas de sens. + +### Négatives + +* Des messages de commit valides aujourd'hui (`feat: …` / `fix: …` sans scope) sont désormais rejetés ; les contributeurs doivent ajouter un scope. +* La convention gagne une règle dépendante du type (scope requis pour `feat`/`fix`, optionnel sinon) au lieu d'un « scope optionnel » uniforme, une légère augmentation de la surface de la règle. + +### Risques + +* Un changement véritablement transverse ou limité aux samples, visible par l'utilisateur, pourrait n'avoir aucun scope unique naturel. Mesure : un changement qui traverse plusieurs composants porte déjà tous leurs scopes (la convention exige la liste complète séparée par des virgules) ; un changement qui n'appartient réellement à aucun composant n'est, par définition, pas un `feat`/`fix` de composant et prend un type qui ne pilote pas la version. +* L'historique et les branches en cours créés avant l'adoption peuvent contenir des `feat`/`fix` sans scope. Mesure : la règle s'applique à partir de son adoption, aux nouveaux commits, cohérent avec CONTRIBUTING → « Adoption » ; l'historique antérieur n'est pas réécrit et les commits de merge restent exemptés. + +## Actions de suivi + +* Appliquer l'exigence dans `tools/commit-lint/lint-commit-message.sh` (fait dans la pull request d'implémentation). +* Énoncer la règle appliquée dans CONTRIBUTING.md et sa traduction française (fait dans la pull request d'implémentation). +* Aucune modification de l'outillage de release n'est nécessaire : la sémantique de partition est inchangée ; seul le linter gagne l'exigence. + +## Références + +* Issue [#293](https://github.com/Reefact/first-class-errors/issues/293) — la présente décision. +* Issue [#231](https://github.com/Reefact/first-class-errors/issues/231) et PR [#292](https://github.com/Reefact/first-class-errors/pull/292) — ont fait surgir et documenté la dérive que cette décision applique. +* `tools/trains.sh`, `tools/packaging/release-notes.sh`, `tools/changelog/collect-prs.sh` — la partition par scope et l'écart des commits sans scope. +* `tools/commit-lint/lint-commit-message.sh` et `.github/workflows/commit-lint.yml` — là où l'exigence est appliquée. +* CONTRIBUTING.md → « Scope » et « Adoption ». diff --git a/doc/handwritten/for-maintainers/adr/0034-require-a-scope-on-the-version-driving-commit-types.md b/doc/handwritten/for-maintainers/adr/0034-require-a-scope-on-the-version-driving-commit-types.md new file mode 100644 index 00000000..f98f6789 --- /dev/null +++ b/doc/handwritten/for-maintainers/adr/0034-require-a-scope-on-the-version-driving-commit-types.md @@ -0,0 +1,75 @@ +# ADR-0034 | Require a scope on the version-driving commit types + +🌍 🇬🇧 English (this file) · 🇫🇷 [Français](0034-require-a-scope-on-the-version-driving-commit-types.fr.md) + +**Status:** Accepted +**Date:** 2026-07-23 +**Decision Makers:** Reefact + +## Context + +The repository publishes several independently-versioned packages grouped into **release trains**; `tools/trains.sh` is the single source of truth mapping each Conventional Commit scope to a train (`lib`, `cli`, `dum`). A train's GitHub Release notes and changelog are generated (`tools/packaging/release-notes.sh`, `tools/changelog/collect-prs.sh`) by selecting the commits whose scope belongs to that train. A commit with no scope belongs to no train and is excluded. That exclusion is intended: infrastructure commits (`ci`, `chore`, `build`, `docs`) carry no scope and should not appear in a package's release record. + +The commit convention (CONTRIBUTING.md) makes the scope syntactically optional on every type. The two **version-driving** types are `feat` (drives `MINOR`) and `fix` (drives `PATCH`); by definition they denote a change visible to the consumer of a package. `commit-lint` — one script shared by the local `commit-msg` hook and the CI check — validates the header shape and, until now, accepted a `feat`/`fix` with no scope. + +The consequence has already been observed and documented (issue #231, PR #292): a contributor following the letter of the convention can write an unscoped, user-facing `feat:`/`fix:`, which then silently disappears from both the release notes and the changelog. The change ships in the binaries yet never appears in the release record, with no error at any point. + +## Decision + +`commit-lint` requires a Conventional Commit scope on the two version-driving types, `feat` and `fix`, and rejects an unscoped one — identically at the `commit-msg` hook and in the CI check — while every other type keeps the scope optional. + +## Rationale + +The release record is partitioned solely by scope, so on the version-driving types the scope is not a readability aid but the routing key that decides whether a consumer-visible change is recorded at all. A `feat`/`fix` without it is not merely less readable — it is dropped from the public account of what shipped, silently. + +Documentation alone cannot close that gap. The rule already stated in CONTRIBUTING depends on the author recalling an invisible consequence at the moment of writing the commit; the failure produces no signal and only surfaces later as a hole a user notices. Moving the rule into `commit-lint` turns a remembered convention into an enforced invariant, caught at write time and — for a bypassed local hook — again in CI, the same layered enforcement the convention already relies on for its other rules. + +Confining the requirement to `feat` and `fix` is what keeps it correct rather than merely strict: only those two types drive a version and denote consumer-visible change. The non-version-driving types are exactly the infrastructure and repository-wide work that legitimately belongs to no train, so requiring a scope there would be noise and would misrepresent repository work as component work. + +The cost — rejecting some messages that are valid today — is bounded and self-correcting: the author adds a scope the moment the hook or CI flags it, guided by a message that names the consequence. The header grammar and the shared hook/CI wiring are implementation, in `tools/commit-lint/lint-commit-message.sh` and the commit-lint workflow — not here. + +## Alternatives Considered + +### Keep documenting the rule, do not enforce it (the state after #231) + +Considered because #231 / #292 already corrected the documentation, so the convention now states the requirement in prose. Rejected because a prose rule against a silent, invisible failure relies on the author remembering it exactly when nothing signals the mistake; the release record can still lose a user-facing change, which is the harm the rule exists to prevent. + +### Require a scope on every type + +Considered because a single, uniform "always scope" rule is the simplest to state and to implement. Rejected because the non-version-driving types are the legitimate unscoped case — infrastructure, repository-wide documentation, ADRs, the `FirstClassErrors.Usage` samples — and forcing a scope there would be noise and would falsely attribute repository work to a component and its train. + +### Change the release tooling to keep unscoped feat/fix instead + +Considered because the drop happens in `release-notes.sh` / `collect-prs.sh`, which could instead route an unscoped version-driving commit into a default train. Rejected because there is no correct default train for a scopeless consumer-visible change: the scope is precisely what identifies which package changed, so guessing would file the change under the wrong package's release record — a subtler, harder-to-notice error than refusing the ambiguous commit at the source. + +## Consequences + +### Positive + +* A user-facing `feat`/`fix` can no longer vanish from the release notes or the changelog; the release record is complete by construction. +* The rule the convention already documents becomes enforced — at write time and in CI — closing the gap #231 could only describe. +* Infrastructure and repository-wide work stays scope-free, so the enforcement adds no friction where a scope would be meaningless. + +### Negative + +* Commit messages valid today (`feat: …` / `fix: …` with no scope) are now rejected; contributors must add a scope. +* The convention gains one type-dependent rule (scope required for `feat`/`fix`, optional otherwise) in place of a single uniform "scope optional", a small increase in the rule's surface. + +### Risks + +* A genuinely cross-cutting or sample-only user-facing change might have no natural single scope. Mitigation: a change crossing several components already carries all their scopes (the convention requires the full comma-separated list); a change that truly belongs to no component is, by definition, not a component `feat`/`fix` and takes a non-version-driving type. +* History and in-flight branches created before adoption may contain unscoped `feat`/`fix`. Mitigation: the rule applies from adoption on, to new commits, consistent with CONTRIBUTING → "Adoption"; prior history is not rewritten and merge commits stay exempt. + +## Follow-up Actions + +* Enforce the requirement in `tools/commit-lint/lint-commit-message.sh` (done in the implementing pull request). +* State the enforced rule in CONTRIBUTING.md and its French translation (done in the implementing pull request). +* No release-tooling change is needed: the partition semantics are unchanged; only the linter gains the requirement. + +## References + +* Issue [#293](https://github.com/Reefact/first-class-errors/issues/293) — this decision. +* Issue [#231](https://github.com/Reefact/first-class-errors/issues/231) and PR [#292](https://github.com/Reefact/first-class-errors/pull/292) — surfaced and documented the drift this decision enforces. +* `tools/trains.sh`, `tools/packaging/release-notes.sh`, `tools/changelog/collect-prs.sh` — the scope-based partition and the unscoped-commit drop. +* `tools/commit-lint/lint-commit-message.sh` and `.github/workflows/commit-lint.yml` — where the requirement is enforced. +* CONTRIBUTING.md → "Scope" and "Adoption". diff --git a/doc/handwritten/for-maintainers/adr/README.md b/doc/handwritten/for-maintainers/adr/README.md index d01e827d..73c7bbd1 100644 --- a/doc/handwritten/for-maintainers/adr/README.md +++ b/doc/handwritten/for-maintainers/adr/README.md @@ -216,3 +216,4 @@ Optional supporting material: | [ADR-0031](0031-name-any-factories-after-their-clr-type.md) | Name Any's scalar factories after their CLR type | Accepted | | [ADR-0032](0032-draw-arbitrary-values-from-an-explicit-top-level-pool.md) | Draw arbitrary values from an explicit, top-level choice pool | Accepted | | [ADR-0033](0033-meet-string-exclusions-with-a-bounded-redraw.md) | Meet string exclusions with a bounded redraw | Accepted | +| [ADR-0034](0034-require-a-scope-on-the-version-driving-commit-types.md) | Require a scope on the version-driving commit types | Accepted | From ee90b6c6e0416e81d69245801d6c15ac28a1f151 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Jul 2026 10:15:41 +0000 Subject: [PATCH 2/3] ci: require a scope on feat and fix in commit-lint feat and fix are the only version-driving types, and the release tooling (tools/trains.sh) routes commits to trains by scope; an unscoped feat/fix matches no train and is silently dropped from the release notes and the changelog, so a user-facing change can vanish from the release record. Reject an unscoped feat/fix at the commit-msg hook and in CI, naming the consequence. Every other type keeps the scope optional. Refs: #293 --- tools/commit-lint/lint-commit-message.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tools/commit-lint/lint-commit-message.sh b/tools/commit-lint/lint-commit-message.sh index 6ff309e8..8c05d802 100755 --- a/tools/commit-lint/lint-commit-message.sh +++ b/tools/commit-lint/lint-commit-message.sh @@ -96,7 +96,17 @@ else # canonical shape: [([,...])][!]: if printf '%s' "$subject" | grep -Eq "^(${TYPES})(\((${SCOPES})(,(${SCOPES}))*\))?!?: [a-z]"; then - : + # Well-formed header. The two version-driving types must ALSO carry a scope: + # the release tooling (tools/trains.sh) partitions commits into trains by + # scope, so an unscoped feat/fix matches no train and is silently dropped + # from the release notes and the changelog. See CONTRIBUTING.md -> "Scope". + case "$subject" in + feat:*|feat!:*|fix:*|fix!:*) + vtype="${subject%%[:!(]*}" + err "a '${vtype}' commit must carry a scope, e.g. '${vtype}(core): …' — the release tooling routes commits to trains by scope, and an unscoped ${vtype} is silently dropped from the release notes and the changelog" + ;; + *) ;; # already scoped, or a non-version-driving type: nothing to require + esac else # --- targeted diagnostics so the author knows exactly what to fix --- if ! printf '%s' "$subject" | grep -Eq '^[^:]+: .'; then From da32c251fd2ee4ec3cba7132b3b2b1e17ba6d849 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 23 Jul 2026 10:15:41 +0000 Subject: [PATCH 3/3] docs: state the enforced feat/fix scope rule in CONTRIBUTING Update the Scope section (EN + FR) from "a user-facing change must carry a scope" to the rule commit-lint now enforces: a scope is required on feat and fix and rejected when absent, at the hook and in CI, and optional on every other type. Link ADR-0034. Refs: #293 --- CONTRIBUTING.md | 20 +++++++++++------- doc/handwritten/for-users/CONTRIBUTING.fr.md | 22 +++++++++++--------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d21f24e8..897e8939 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -290,7 +290,8 @@ by any of these types produces a `MAJOR`. #### Scope -The scope MAY be provided. When present it MUST be lowercase and MUST be one of: +The scope MAY be provided, and is **required** on `feat` and `fix` (see below). +When present it MUST be lowercase and MUST be one of: | Scope | Covers | |---|---| @@ -315,13 +316,16 @@ and `dum` (scope `dummies` → `Dummies`). A commit's scope decides which train' release notes and changelog it lands in; see [Adding a release train](doc/handwritten/for-maintainers/AddingAReleaseTrain.en.md). -Because of that, a user-facing change **must** carry a scope: an unscoped `feat:` -or `fix:` matches no train and is **silently dropped from every release note and -changelog**, so the change would vanish from the release record. Only what -genuinely belongs to no component stays unscoped — repository infrastructure (the -solution, `Directory.Build.props`, the workflows, `.gitignore`, `CLAUDE.md`), -repository-wide documentation, ADRs, and the `FirstClassErrors.Usage` samples — -and those use non-version-driving types anyway: `ci: …`, `docs: …`, `chore: …`. +Because of that, **`commit-lint` requires a scope on the two version-driving +types, `feat` and `fix`**, and rejects an unscoped one at the `commit-msg` hook +and in CI ([ADR-0034](doc/handwritten/for-maintainers/adr/0034-require-a-scope-on-the-version-driving-commit-types.md)): +it would match no train and be **silently dropped from every release note and +changelog**, vanishing from the release record. Every other type keeps the scope +optional — what genuinely belongs to no component stays unscoped (repository +infrastructure: the solution, `Directory.Build.props`, the workflows, +`.gitignore`, `CLAUDE.md`; repository-wide documentation; ADRs; the +`FirstClassErrors.Usage` samples), and those use non-version-driving types +anyway: `ci: …`, `docs: …`, `chore: …`. When one atomic change crosses several components, the commit MUST carry all their scopes, comma-separated with no space and ordered alphabetically. The diff --git a/doc/handwritten/for-users/CONTRIBUTING.fr.md b/doc/handwritten/for-users/CONTRIBUTING.fr.md index 694a778a..98f6ca4c 100644 --- a/doc/handwritten/for-users/CONTRIBUTING.fr.md +++ b/doc/handwritten/for-users/CONTRIBUTING.fr.md @@ -301,8 +301,8 @@ n’importe lequel de ces types produit un `MAJOR`. #### Scope -Le scope PEUT être fourni. Lorsqu’il est présent, il DOIT être en minuscules et DOIT être -l’un des suivants : +Le scope PEUT être fourni, et il est **requis** sur `feat` et `fix` (voir plus bas). Lorsqu’il +est présent, il DOIT être en minuscules et DOIT être l’un des suivants : | Scope | Couvre | |---|---| @@ -326,14 +326,16 @@ publie indépendamment : `lib` (scopes `core`, `analyzers`, `testing`, `binder` d’un commit décide dans les release notes et le changelog de quel train il atterrit ; voir [Ajouter un train de release](../for-maintainers/AddingAReleaseTrain.fr.md). -À cause de cela, un changement visible par l’utilisateur **doit** porter un scope : un `feat:` -ou `fix:` sans scope ne correspond à aucun train et est **silencieusement écarté de toutes les -release notes et de tous les changelogs**, de sorte que le changement disparaîtrait du dossier -de release. Seul ce qui n’appartient réellement à aucun composant reste sans scope — -l’infrastructure du dépôt (la solution, `Directory.Build.props`, les workflows, `.gitignore`, -`CLAUDE.md`), la documentation à l’échelle du dépôt, les ADR, et les exemples de -`FirstClassErrors.Usage` — et ceux-ci emploient de toute façon des types qui ne pilotent pas -la version : `ci: …`, `docs: …`, `chore: …`. +À cause de cela, **`commit-lint` exige un scope sur les deux types qui pilotent la version, +`feat` et `fix`**, et rejette un commit sans scope au hook `commit-msg` et en CI +([ADR-0034](../for-maintainers/adr/0034-require-a-scope-on-the-version-driving-commit-types.fr.md)) : +il ne correspondrait à aucun train et serait **silencieusement écarté de toutes les release +notes et de tous les changelogs**, disparaissant du dossier de release. Tout autre type garde +le scope optionnel — ce qui n’appartient réellement à aucun composant reste sans scope +(l’infrastructure du dépôt : la solution, `Directory.Build.props`, les workflows, `.gitignore`, +`CLAUDE.md` ; la documentation à l’échelle du dépôt ; les ADR ; les exemples de +`FirstClassErrors.Usage`), et ceux-ci emploient de toute façon des types qui ne pilotent pas la +version : `ci: …`, `docs: …`, `chore: …`. Lorsqu’un changement atomique traverse plusieurs composants, le commit DOIT porter tous leurs scopes, séparés par des virgules sans espace et classés par ordre alphabétique.