Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-10
98 changes: 98 additions & 0 deletions openspec/changes/gate-119-procedure-named-classes/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Design: gate-119 procedure-named-classes

## What counts

A file counts when all of these hold:

1. Its path is under `lib/` or `src/`.
2. Its extension is `.php`, `.vue`, `.js` or `.ts`.
3. It is not under `lib/Migration/`, `lib/Settings/`, `src/manifest.json` or `src/manifest.d/`. Configuration is where a procedure name belongs.
4. One of its path tokens is in the vocabulary.

Tokens come from every path segment below `lib/` or `src/`, with the extension stripped. Each segment is split on CamelCase, digits, `-` and `_`, lowercased, and the whole lowercased segment is added as well. `WOODeadlineService.php` gives `woo`, `deadline`, `service` and `woodeadlineservice`. `Service/Bezwaar/HearingService.php` counts through its directory.

A file with a procedure token is **procedure**, even when it also has a standard token. A file with only a standard token is **standard**.

## The vocabulary

`hydra-gates/contracts/procedure-vocabulary.json`:

```json
{
"procedure": ["woo", "wob", "bezwaar", "beroep", "beschikking", "vth", "handhaving", "toezicht",
"omgevingsvergunning", "vergunning", "vergunningaanvraag", "termijn", "subsidie", "besluit",
"besluitvorming", "beslistermijn", "leerplicht", "verzuim", "bpv", "verlof", "dwangsom", "lhs",
"mandaat", "ingebrekestelling", "aanvullingsverzoek", "samenwerkverzoek", "hoorzitting", "wmo",
"jeugdwet", "participatiewet", "bibob", "cao", "poortwachter", "awb", "sociaaldomein",
"staatssteun", "terugvordering", "vaststelling", "tussenrapportage", "bewijsstuk",
"cofinanciering", "uitbetaling", "zaakdossier", "informatieobject", "deelzaak"],
"standard": ["zgw", "zrc", "ztc", "drc", "brc", "nrc", "stuf", "zkn", "brp", "kvk", "bag", "pdok",
"haalcentraal", "dso", "digid", "eherkenning", "peppol", "sbr", "xbrl", "diwoo", "tenderned",
"digipoort", "libresign", "verzuimloket", "iwmo", "ijw"]
}
```

An app may add tokens in its baseline file under `extraProcedureTokens`. It may not remove shared ones.

Domain nouns that are not statutory procedures stay out: `leave` (humaniq), `dunning` and `vat` (shillinq). They are what those apps are for.

## The baseline file

`.hydra/procedure-names.json` in the app repo:

```json
{
"gate": "procedure-named-classes",
"adr": "ADR-118",
"procedure": ["lib/Service/WOODecisionService.php", "..."],
"standard": ["lib/Service/Stuf/StufHttpClient.php", "..."],
"extraProcedureTokens": []
}
```

- `procedure` is the allowlist of procedure-named files. It only shrinks.
- `standard` is the standards-adapter allowlist. A standards file outside it is a warning in a leaf app, and passes in integriq.
- `--write-baseline` writes the file from the current tree, sorted. A lane runs it once at adoption. After that a lane only deletes lines.

## The verdict

| Check | Result |
|---|---|
| A procedure-named file at head is not in `procedure` | FAIL, names the file and the token |
| Procedure count at head is above the count at the base ref | FAIL, prints base, head and delta |
| A standards-named file at head is not in `standard`, in a repo other than integriq | WARN, names integriq or filinq as the home |
| A baseline entry no longer exists | INFO, listed under `--shrinkable` |
| No baseline file and no procedure-named file | PASS |
| No baseline file and procedure-named files exist | FAIL, says to run `--write-baseline` |
| No `lib/` and no `src/` | NOT APPLICABLE |

The base-ref count makes a stale baseline harmless. A deleted file that stays in the baseline cannot come back, because the count would rise.

## Exceptions

A docblock or leading comment `@procedure-name exclude <reason>` removes a file from the head count. The reason is graded by the shared `exclusion_reason.is_reason_bearing()`. A bare marker does not count. Expected uses: a deprecated shim that forwards to a generic class for one release.

## Scope

The count is app-wide, as the custom-widget ratchet does. A scoped run still counts the whole tree, because a rename in one file changes the total. Only the new-file check is diff-scoped: it looks at files added or renamed against `HYDRA_GATE_BASE_REF`.

## Adoption numbers

Measured on `origin/development`, 10 October 2026, with the vocabulary above:

| App | Procedure | Standard |
|---|---:|---:|
| dossiq | 229 | 109 |
| humaniq | 8 | 1 |
| shillinq | 8 | 18 |
| learniq | 5 | 0 |
| pipelinq | 3 | 30 |
| portaliq | 3 | 0 |
| filinq | 1 | 1 |
| decidiq | 0 | 0 |
| integriq | 0 | 127 |
| openregister | 0 | 4 |

## Why a checked-in baseline

The header-action budget gate and others compare against the base ref only, and keep no file to go stale. This gate needs the list as well as the count. A count alone lets a pull request delete one Woo class and add one bezwaar class. The list makes the new file visible by name, and the count keeps the list from mattering when it is stale.
28 changes: 28 additions & 0 deletions openspec/changes/gate-119-procedure-named-classes/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
kind: code
---

## Why

ADR-118 (hydra, `openspec/architecture/adr-118-procedures-are-configuration.md`) records decisions 182, 184 and 185: a Dutch procedure ships as configuration, and code is generic and named by what it does. Decision 182 says a gate will refuse new classes named after a procedure.

On 10 October 2026 dossiq carried 229 procedure-named files and 109 standards-named files under `lib/` and `src/`. learniq, humaniq, shillinq, pipelinq, portaliq and filinq carry 28 procedure-named files between them. Each one arrived as a reasonable-looking class in a reasonable-looking pull request. Without a mechanical check the 230th arrives the same way, while the refactor programme is moving the first 229 out.

## What changes

- A new gate, **gate-119 `procedure-named-classes`**, in `conduction/hydra-gates`.
- A shared vocabulary at `hydra-gates/contracts/procedure-vocabulary.json` with two lists: procedure tokens and standard tokens.
- A per-app baseline file, `.hydra/procedure-names.json`, listing the procedure-named files that exist when the app adopts the gate, and the standards adapters the app is allowed to keep.
- The gate fails when a pull request adds a procedure-named file that is not in the baseline, or when the head count exceeds the base count.
- The gate reports standards-named files in a leaf app as a warning, and names integriq or filinq as their home.
- A `--write-baseline` mode generates the file, and a `--shrinkable` report lists baseline entries that no longer exist.

## Impact

- Every app with `lib/` or `src/` runs it. decidiq, integriq and openregister start at zero procedure-named files.
- dossiq starts at 229. The refactor lanes shrink the baseline as they delete classes.
- No existing file fails on adoption: the baseline is the current tree.

## Numbering

gate-117 is open in #782 and gate-118 in #836. This change takes 119. ADR-118 is the hydra ADR it enforces.
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
## ADDED Requirements

### Requirement: A new procedure-named file fails the gate

The `procedure-named-classes` gate SHALL fail when a file under `lib/` or `src/` with a procedure token in its path exists at head and is not listed in the app's `.hydra/procedure-names.json` `procedure` array. JSON, `lib/Settings/`, `lib/Migration/` and `src/manifest*` SHALL NOT be counted.

#### Scenario: A new Woo service

- **GIVEN** dossiq's baseline does not list `lib/Service/WooThroughputService.php`
- **WHEN** a pull request adds that file
- **THEN** the gate fails
- **AND** prints the path and the token `woo`
- **AND** points at ADR-118

#### Scenario: Procedure names in a package are fine

- **WHEN** a pull request adds `lib/Settings/packages/woo-verzoek/terms.json`
- **THEN** the gate does not count it

#### Scenario: A rename keeps the procedure name

- **GIVEN** `lib/Service/WOODecisionService.php` is in the baseline
- **WHEN** a pull request moves it to `lib/Service/Woo/DecisionService.php`
- **THEN** the gate fails on the new path

### Requirement: The procedure count does not grow

The gate SHALL count procedure-named files at head and at `HYDRA_GATE_BASE_REF`. It SHALL fail when the head count is higher, and SHALL print base, head and delta on every run.

#### Scenario: A deleted file comes back

- **GIVEN** a previous pull request deleted `lib/Service/WOORedactionService.php` and left it in the baseline
- **WHEN** a later pull request adds the file again
- **THEN** the gate fails on the count, base 228 and head 229

#### Scenario: A move shrinks the count

- **WHEN** a pull request deletes eight bezwaar hearing classes and adds `lib/Service/Session/SessionService.php`
- **THEN** the gate passes and prints a delta of minus eight

### Requirement: Standards adapters are reported, not refused

A file with only a standard token SHALL NOT count as procedure. In a repo other than integriq, a standards file that is not in the baseline `standard` array SHALL produce a warning that names integriq, or filinq for signing, as its home. The warning SHALL NOT fail the gate.

#### Scenario: A new StUF class in dossiq

- **WHEN** a pull request adds `lib/Service/Stuf/StufAckParser.php` to dossiq
- **THEN** the gate warns that StUF adapters belong in integriq
- **AND** passes

### Requirement: Baseline tooling

The gate SHALL offer `--write-baseline`, which writes `.hydra/procedure-names.json` from the current tree, sorted, and `--shrinkable`, which lists baseline entries that no longer exist. Without a baseline file, the gate SHALL pass when the app has no procedure-named file, and SHALL fail with an instruction to run `--write-baseline` otherwise.

#### Scenario: First adoption

- **GIVEN** humaniq has no baseline file and 8 procedure-named files
- **WHEN** the gate runs
- **THEN** it fails and prints the `--write-baseline` command
- **WHEN** the lane runs `--write-baseline` and commits the file
- **THEN** the gate passes

### Requirement: A reason-bearing exclusion

A file carrying `@procedure-name exclude <reason>` in its first docblock or leading comment SHALL be left out of the head count and the new-file check. The reason SHALL pass `exclusion_reason.is_reason_bearing()`. A bare marker SHALL NOT count.

#### Scenario: A one-release shim

- **WHEN** `lib/Service/TermijnService.php` forwards to `lib/Service/Term/TermService.php` and carries `@procedure-name exclude forwards to TermService for one release, removed in 0.5`
- **THEN** the gate leaves it out of the count

### Requirement: Applicability

The gate SHALL report NOT APPLICABLE when the app has neither `lib/` nor `src/`. It SHALL be listed in `run-hydra-gates.sh` as gate-119 and SHALL take part in the COVERAGE line.

#### Scenario: A Python ExApp

- **WHEN** the gate runs on a repo with no `lib/` and no `src/`
- **THEN** it reports NOT APPLICABLE with that reason
26 changes: 26 additions & 0 deletions openspec/changes/gate-119-procedure-named-classes/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Tasks: gate-119 procedure-named-classes

Building is a separate step from this spec (refactor lane R10).

## 1. Vocabulary and helper

- [ ] 1.1 Add `hydra-gates/contracts/procedure-vocabulary.json` with the two lists from design.md
- [ ] 1.2 Add `hydra-gates/scripts/lib/check_procedure_named_classes.py`: tokenise paths, count at head and at `HYDRA_GATE_BASE_REF` (via `git ls-tree`, no checkout), compare with `.hydra/procedure-names.json`
- [ ] 1.3 `--write-baseline` and `--shrinkable` modes
- [ ] 1.4 Honour `@procedure-name exclude <reason>` through `exclusion_reason.is_reason_bearing()`

## 2. Tests and fixtures

- [ ] 2.1 `test_check_procedure_named_classes.py`: new file fails, rename fails, re-added deleted file fails on the count, move passes with a negative delta, JSON under `lib/Settings/` is ignored, standards file warns, bare exclusion marker counts
- [ ] 2.2 Planted fixture under `scripts/test-fixtures/` with one procedure file outside the baseline; the gate must fail on it

## 3. Wiring

- [ ] 3.1 Register gate-119 in `run-hydra-gates.sh`, with NOT APPLICABLE for repos without `lib/` and `src/`
- [ ] 3.2 Add the skill `hydra-gate-procedure-named-classes` to hydra `.claude/skills/`
- [ ] 3.3 Document the gate in `hydra-gates/README.md`

## 4. Adoption

- [ ] 4.1 One pull request per core app with `.hydra/procedure-names.json` from `--write-baseline`
- [ ] 4.2 Bump `conduction/hydra-gates` in each app after 4.1 lands
Loading