diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 4fbb41d..986c65f 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "pathmode", "displayName": "Pathmode", - "version": "0.1.44", + "version": "0.1.45", "description": "Run a deterministic preflight on your intent before an agent builds: /preflight scores six calibrated gates and names the exact blockers, no model call, no key. Pathmode also interrogates the intent behind a request (compile-intent), writes it to intent.md in your repo, and checks the code that comes back against the outcomes and constraints you agreed to. Free: uses the models you already have access to in Claude Code. Connect a Pathmode workspace to sync intent and evidence across a team.", "author": { "name": "Pathmode", diff --git a/.mcp.json b/.mcp.json index b656d42..eb1f266 100644 --- a/.mcp.json +++ b/.mcp.json @@ -2,7 +2,7 @@ "mcpServers": { "pathmode": { "command": "npx", - "args": ["-y", "@pathmode/mcp-server@1.37.0"], + "args": ["-y", "@pathmode/mcp-server@1.38.0"], "env": { "PATHMODE_API_KEY": "${user_config.api_key}" } diff --git a/CALIBRATION.md b/CALIBRATION.md index c2e4413..ef58a22 100644 --- a/CALIBRATION.md +++ b/CALIBRATION.md @@ -23,7 +23,7 @@ Three things get confused here, and they are not the same claim: 3. **Whether the spec describes the right product decision.** **Nothing here measures this**, and no corpus of this shape could. A spec can score 6/6 and be a bad idea, clearly stated. -Part A is a **regression baseline, not independent validation.** These 111 fixtures were written +Part A is a **regression baseline, not independent validation.** These 192 fixtures were written alongside the rules they score, and the rules were tuned until they agreed. A high score is therefore evidence that the gate still behaves as it did when it was calibrated. It is not evidence that the gate generalizes. Independently collected, previously unseen specs would be @@ -76,7 +76,7 @@ Each case records **where its defect actually shipped**, and they are not all eq | Case | Provenance | |---|---| | prose sections are content, not absence | shipped in `<= 1.26.3`, fixed in `1.27.0` | -| non-English is unconfirmed, never absent | the known limitation the four-state verdict reports honestly | +| an unread language is unconfirmed, never absent | the known limitation the four-state verdict reports honestly | | `## Why` is the objective, `## What` is not the outcomes | shipped in `<= 1.36.1`, fixed in `1.37.0` | | italic text the author wrote is content | never released; introduced and fixed between two commits | | a subsection heading is not a verification check | never released; same | @@ -106,9 +106,10 @@ nothing was written. ## Known limitations, stated plainly -- The gates match a **fixed English vocabulary**. A non-English objective cannot be confirmed, and - reports as `unconfirmed`. Part B has a Finnish case that holds this behaviour honest. -- 111 fixtures is small, and they are **our own authored examples**, not sampled from real repos. +- The gates match a **fixed English and Finnish vocabulary**. An objective in any other language + cannot be confirmed, and reports as `unconfirmed`. Part B has a German case that holds this + behaviour honest. +- 192 fixtures is small, and they are **our own authored examples**, not sampled from real repos. - The gates judge **phrasing**, not correctness, novelty, feasibility, or whether the decision is a good one. - Part A cannot detect a document-parsing regression, as shown above. diff --git a/README.md b/README.md index 787550e..76eccd7 100644 --- a/README.md +++ b/README.md @@ -72,9 +72,9 @@ For workspace API access without a local intent, you can still create an API key ## What's bundled -**MCP server** — `@pathmode/mcp-server@1.37.0`, pinned so the plugin skills and server tool contract update together. Local mode with no key; cloud mode with one. +**MCP server** — `@pathmode/mcp-server@1.38.0`, pinned so the plugin skills and server tool contract update together. Local mode with no key; cloud mode with one. -**Check the gate yourself** — `node scripts/readiness-suite.mjs` runs the pinned server's preflight over 111 labelled field fixtures and a set of whole `intent.md` documents, and prints where it disagrees. Read [CALIBRATION.md](CALIBRATION.md) first: the field score is a regression baseline, not an accuracy claim. +**Check the gate yourself** — `node scripts/readiness-suite.mjs` runs the pinned server's preflight over 192 labelled field fixtures and a set of whole `intent.md` documents, and prints where it disagrees. Read [CALIBRATION.md](CALIBRATION.md) first: the field score is a regression baseline, not an accuracy claim. **Command** — `/preflight` runs the deterministic six-gate readiness check and always ends in a verdict: on your `intent.md` if one exists, on a spec described in the conversation, or on a provisional draft it builds from context with assumptions marked. @@ -109,9 +109,9 @@ completely silent in repos with no intent. If you would rather it did not run, r against. That tuning set is in this repo, mirrored from the monorepo that owns it, so you can check it rather than take the number on faith. -[`readiness-corpus.json`](./readiness-corpus.json) holds 111 hand-labeled spec fragments, 58 labeled -`good` and 53 labeled `vague`, spread across the gates: 24 objectives, 37 outcomes, 20 titles, -18 constraints, 12 verification checks. Each item carries the text, the label, and the tags that +[`readiness-corpus.json`](./readiness-corpus.json) holds 192 hand-labeled spec fragments (121 English, 71 Finnish), +101 labeled `good` and 91 labeled `vague`, spread across the gates: 44 objectives, 78 outcomes, +27 titles, 27 constraints, 16 verification checks. Each item carries the text, the label, and the tags that explain the call: ```json diff --git a/fixtures/documents.json b/fixtures/documents.json index db94388..47f21cf 100644 --- a/fixtures/documents.json +++ b/fixtures/documents.json @@ -68,10 +68,10 @@ } }, { - "name": "non-English is unconfirmed, never absent", + "name": "an unread language is unconfirmed, never absent", "file": "non-english-prose.md", - "why": "The gates match a fixed English vocabulary, so they cannot confirm this objective or constraint. That is a limit of the gate, and the verdict has to say so: 'unconfirmed' means it read the text and its word lists could not confirm it, which is different from claiming nothing was written. An audit of real specs found this class of failure is usually ours, not the author's.", - "provenance": "the known limitation the four-state verdict exists to report honestly", + "why": "The gates match a fixed English and Finnish vocabulary, so they cannot confirm this German objective or constraint. That is a limit of the gate, and the verdict has to say so: 'unconfirmed' means it read the text and its word lists could not confirm it, which is different from claiming nothing was written. An audit of real specs found this class of failure is usually ours, not the author's.", + "provenance": "the known limitation the four-state verdict exists to report honestly; the sample was Finnish until 1.38.0 started grading Finnish", "expect": { "objective": "unconfirmed", "constraints": "unconfirmed" } }, { diff --git a/fixtures/documents/non-english-prose.md b/fixtures/documents/non-english-prose.md index e1cb4f0..dd1c8bc 100644 --- a/fixtures/documents/non-english-prose.md +++ b/fixtures/documents/non-english-prose.md @@ -1,11 +1,11 @@ --- -title: "Parempi hakukokemus" +title: "Bessere Sucherfahrung" --- -# Parempi hakukokemus +# Bessere Sucherfahrung ## Objective -Käyttäjät eivät löydä tuotteita hakutoiminnolla, koska haku ei ymmärrä taivutusmuotoja. +Nutzer finden keine Produkte über die Suche, weil die Suche keine Flexionsformen versteht. ## Constraints -Ei saa hidastaa sivun latausta. +Darf die Seite nicht verlangsamen. diff --git a/readiness-corpus.json b/readiness-corpus.json index bd1956d..b42f5ae 100644 --- a/readiness-corpus.json +++ b/readiness-corpus.json @@ -642,6 +642,106 @@ "no-threshold" ] }, + { + "kind": "outcome", + "text": "An import with a duplicate ID is refused and names the existing row.", + "label": "good", + "tags": [ + "refusal", + "names-the-case", + "refusal-names" + ] + }, + { + "kind": "outcome", + "text": "Merging is refused until every required reviewer has approved the change.", + "label": "good", + "tags": [ + "refusal", + "refusal-condition" + ] + }, + { + "kind": "outcome", + "text": "A token issued for another workspace is rejected.", + "label": "good", + "tags": [ + "refusal", + "names-the-case" + ] + }, + { + "kind": "outcome", + "text": "Checkout rejects an expired card before it is charged.", + "label": "good", + "tags": [ + "refusal", + "refusal-condition", + "active-voice" + ] + }, + { + "kind": "outcome", + "text": "Every exported EIR row names the PIR or AIR it serves and the decision point it must be delivered by; an export with a row missing either is refused and the refusal names the row.", + "label": "good", + "tags": [ + "refusal", + "names-the-case", + "refusal-names", + "rytv-demo-2026-09-27" + ] + }, + { + "kind": "outcome", + "text": "Bad requests are rejected.", + "label": "vague", + "tags": [ + "refusal", + "bare-category", + "genuinely-vague" + ] + }, + { + "kind": "outcome", + "text": "Invalid input is refused.", + "label": "vague", + "tags": [ + "refusal", + "bare-category", + "genuinely-vague" + ] + }, + { + "kind": "outcome", + "text": "Harmful prompts are refused.", + "label": "vague", + "tags": [ + "refusal", + "bare-category", + "genuinely-vague" + ] + }, + { + "kind": "outcome", + "text": "Dangerous changes are blocked.", + "label": "vague", + "tags": [ + "refusal", + "bare-category", + "blocked-synonym", + "genuinely-vague" + ] + }, + { + "kind": "outcome", + "text": "Rejected ideas are handled properly.", + "label": "vague", + "tags": [ + "refusal", + "adjective-not-verb", + "genuinely-vague" + ] + }, { "kind": "title", "text": "Bulk edit for evidence items", @@ -1053,5 +1153,795 @@ "tags": [ "placeholder" ] + }, + { + "kind": "objective", + "text": "Työnhakijat menettävät haastatteluaikoja, koska kalenterikutsut päätyvät roskapostiin eikä niitä lähetetä uudelleen.", + "label": "good", + "tags": [ + "fi", + "translation", + "named-role", + "problem-framing", + "plural-actor" + ] + }, + { + "kind": "objective", + "text": "Vähennä haastatteluista poisjääntejä työnhakijoilla, jotka eivät löydä kokouslinkkiä ajoissa.", + "label": "good", + "tags": [ + "fi", + "translation", + "named-role", + "problem-framing" + ] + }, + { + "kind": "objective", + "text": "Mahdollista kiireisille vanhemmille tilin käyttöönoton viimeistely alle 15 minuutissa ilman tukipuhelua.", + "label": "good", + "tags": [ + "fi", + "translation", + "named-role", + "capability-framing", + "implicit-numeric" + ] + }, + { + "kind": "objective", + "text": "Anna kuraattoreille tapa hylätä heikot ehdotukset kerralla sen sijaan, että jokainen arvioidaan erikseen.", + "label": "good", + "tags": [ + "fi", + "translation", + "named-role", + "capability-framing", + "allative-actor" + ] + }, + { + "kind": "objective", + "text": "Perustajat keskeyttävät hinnoittelusivun kesken, koska käyttäjäkohtaista hintalaskelmaa ei näytetä ennen kassaa.", + "label": "good", + "tags": [ + "fi", + "translation", + "named-role", + "problem-framing", + "plural-actor" + ] + }, + { + "kind": "objective", + "text": "Perustajien pitäisi nähdä yhdellä silmäyksellä, mitkä heidän kokeiluistaan tuovat yhä liikennettä.", + "label": "good", + "tags": [ + "fi", + "translation", + "plural-actor", + "capability-framing", + "observable-no-number" + ] + }, + { + "kind": "objective", + "text": "Jake ei pysty täsmäyttämään vietyjä laskuja, koska rivit jakautuvat sivuille ilman välisummia.", + "label": "good", + "tags": [ + "fi", + "translation", + "named-person", + "problem-framing" + ] + }, + { + "kind": "objective", + "text": "Anna ensikertalaisille työnhakijoille mahdollisuus esikatsella, miltä heidän profiilinsa näyttää rekrytoijille.", + "label": "good", + "tags": [ + "fi", + "translation", + "named-role", + "capability-framing", + "compound-actor" + ] + }, + { + "kind": "objective", + "text": "Tilaajaorganisaatiot, kuten kaupungit ja Senaatti-kiinteistöt, sekä niiden puolesta vaatimuksia kirjoittavat konsultit määrittelevät tietomallinnuksen tietovaatimukset käsin RYTV-tilaajaohjeen Word- ja Excel-pohjiin. Jokaisen EIR-rivin pitäisi palvella hankkeen tietovaatimusta (PIR), mutta Liite 2 -taulukossa linkki on käsin kirjoitettu sarake, joten mikään ei estä vaatimusta päätymästä tarjouspyyntöön ilman PIR-linkkiä.", + "label": "good", + "tags": [ + "fi", + "native", + "rytv-probe-2026-09-27", + "compound-actor", + "problem-framing" + ] + }, + { + "kind": "objective", + "text": "Opiskelija ei ymmärrä, miksi ilmoittautuminen kurssille ei onnistu.", + "label": "good", + "tags": [ + "fi", + "native", + "named-role", + "problem-framing" + ] + }, + { + "kind": "objective", + "text": "Kehitystiimit menettävät päivän jokaisessa julkaisussa, koska ympäristöjen asetukset kopioidaan käsin.", + "label": "good", + "tags": [ + "fi", + "native", + "compound-actor", + "problem-framing" + ] + }, + { + "kind": "objective", + "text": "Tee kojelaudasta parempi.", + "label": "vague", + "tags": [ + "fi", + "translation", + "genuinely-vague", + "no-actor", + "platitude" + ] + }, + { + "kind": "objective", + "text": "Paranna yleistä käyttöönottokokemusta, jotta käyttäjät olisivat tyytyväisempiä.", + "label": "vague", + "tags": [ + "fi", + "translation", + "genuinely-vague", + "platitude", + "no-concrete-change" + ] + }, + { + "kind": "objective", + "text": "Tee kaikesta nopeampaa ja laadukkaampaa.", + "label": "vague", + "tags": [ + "fi", + "translation", + "genuinely-vague", + "no-actor", + "platitude" + ] + }, + { + "kind": "objective", + "text": "Paranna kojelaudan yleistä käyttökokemusta.", + "label": "vague", + "tags": [ + "fi", + "translation", + "improve-ux", + "genuinely-vague", + "no-concrete-change" + ] + }, + { + "kind": "objective", + "text": "Optimoi suppilo ja saa aikaan parempia tuloksia.", + "label": "vague", + "tags": [ + "fi", + "translation", + "make-x-better", + "genuinely-vague", + "no-concrete-change" + ] + }, + { + "kind": "objective", + "text": "Tarjoa luokkansa paras, saumaton kokemus, joka ilahduttaa käyttäjiä jokaisessa kohtaamisessa.", + "label": "vague", + "tags": [ + "fi", + "translation", + "genuinely-vague", + "buzzwords", + "implicit-actor" + ] + }, + { + "kind": "objective", + "text": "Hyödynnä huippumodernia tekoälyä mullistamaan tapa, jolla tiimit vapauttavat koko potentiaalinsa.", + "label": "vague", + "tags": [ + "fi", + "translation", + "genuinely-vague", + "buzzwords", + "plural-actor" + ] + }, + { + "kind": "objective", + "text": "Anna käyttäjille mahdollisuus tehdä saumattomasti enemmän pienemmällä kitkalla alusta loppuun.", + "label": "vague", + "tags": [ + "fi", + "translation", + "genuinely-vague", + "buzzwords", + "plural-actor" + ] + }, + { + "kind": "objective", + "text": "Uudista seuraavan sukupolven työnkulku älykkäämmäksi, nopeammaksi ja ilahduttavammaksi.", + "label": "vague", + "tags": [ + "fi", + "translation", + "genuinely-vague", + "buzzwords", + "no-actor" + ] + }, + { + "kind": "outcome", + "text": "Käyttäjät voivat jatkaa luonnosta miltä tahansa laitteelta ja palaavat täsmälleen siihen kenttään, johon jäivät.", + "label": "good", + "tags": [ + "fi", + "translation", + "plural-actor", + "capability-framing" + ] + }, + { + "kind": "outcome", + "text": "Käyttöönoton läpäisyaste nousee 75 prosenttiin ensimmäisen istunnon aikana.", + "label": "good", + "tags": [ + "fi", + "translation", + "numeric" + ] + }, + { + "kind": "outcome", + "text": "CSV-viennit eivät enää pudota rivejä, kun aineisto ylittää yhden tulossivun.", + "label": "good", + "tags": [ + "fi", + "translation", + "observable-state", + "no-number" + ] + }, + { + "kind": "outcome", + "text": "Jokainen uusi tuotetyötila on täysin valmiiksi täytetty esimerkkiaineistolla ja yhdellä aloitusintentillä.", + "label": "good", + "tags": [ + "fi", + "translation", + "observable-state" + ] + }, + { + "kind": "outcome", + "text": "Ylläpitäjät voivat avata vain luku -esikatselutilan ja poistua siitä ilman, että heidän istuntonsa kirjautuu ulos.", + "label": "good", + "tags": [ + "fi", + "translation", + "capability-framing", + "named-role" + ] + }, + { + "kind": "outcome", + "text": "Tuonnin uudelleenajo epäonnistuneen synkronoinnin jälkeen ei tuota kaksoiskappaleita.", + "label": "good", + "tags": [ + "fi", + "translation", + "observable-state", + "no-number" + ] + }, + { + "kind": "outcome", + "text": "Yöllinen täsmäytys jättää kirjanpitoon nolla täsmäämätöntä tietuetta.", + "label": "good", + "tags": [ + "fi", + "translation", + "word-number", + "bare-zero", + "no-digit" + ] + }, + { + "kind": "outcome", + "text": "Hinnastosta jää yhdistämisen jälkeen voimaan vain yksi kopio.", + "label": "good", + "tags": [ + "fi", + "translation", + "word-number", + "quantifier", + "only-one" + ] + }, + { + "kind": "outcome", + "text": "Haarautumien tunnistus havaitsee kaikki kolme lisättyä haaraa jokaisella harjoitusajolla.", + "label": "good", + "tags": [ + "fi", + "translation", + "word-number", + "quantifier", + "all-n" + ] + }, + { + "kind": "outcome", + "text": "Jokainen julkaisu toimitetaan enintään kahdella manuaalisella QA-hyväksynnällä.", + "label": "good", + "tags": [ + "fi", + "translation", + "word-number", + "quantifier", + "at-most" + ] + }, + { + "kind": "outcome", + "text": "Jokainen viety EIR-rivi nimeää PIR- tai AIR-vaatimuksen, jota se palvelee, ja päätöspisteen, johon mennessä se on toimitettava; vienti, josta jompikumpi puuttuu, estetään ja esto nimeää rivin.", + "label": "good", + "tags": [ + "fi", + "native", + "rytv-probe-2026-09-27", + "observable-state" + ] + }, + { + "kind": "outcome", + "text": "Ennen vientiä palvelu listaa jokaisen PIR-vaatimuksen, jota mikään EIR-rivi ei palvele, jotta laatija näkee aukot ennen tarjouspyynnön lähtöä.", + "label": "good", + "tags": [ + "fi", + "native", + "rytv-probe-2026-09-27", + "capability-framing" + ] + }, + { + "kind": "outcome", + "text": "Tilaaja voi viedä hankkeen kaikki vaatimukset avoimissa muodoissa ja avata ne ilman palvelua.", + "label": "good", + "tags": [ + "fi", + "native", + "rytv-probe-2026-09-27", + "capability-framing" + ] + }, + { + "kind": "outcome", + "text": "Tee kokemuksesta parempi kaikille.", + "label": "vague", + "tags": [ + "fi", + "translation", + "genuinely-vague" + ] + }, + { + "kind": "outcome", + "text": "Käyttäjät ovat tyytyväisiä ja tuote tuntuu nopeammalta.", + "label": "vague", + "tags": [ + "fi", + "translation", + "genuinely-vague", + "plural-actor" + ] + }, + { + "kind": "outcome", + "text": "Paranna yleistä laatua kautta linjan.", + "label": "vague", + "tags": [ + "fi", + "translation", + "genuinely-vague" + ] + }, + { + "kind": "outcome", + "text": "Tuote tuntuu nopeammalta ja hiotummalta.", + "label": "vague", + "tags": [ + "fi", + "translation", + "genuinely-vague", + "unverifiable" + ] + }, + { + "kind": "outcome", + "text": "Luokkansa paras kassa, jota asiakkaat rakastavat.", + "label": "vague", + "tags": [ + "fi", + "translation", + "platitude", + "unverifiable" + ] + }, + { + "kind": "outcome", + "text": "Sitoutuneisuus paranee merkityksellisesti kautta linjan.", + "label": "vague", + "tags": [ + "fi", + "translation", + "genuinely-vague", + "improve-x" + ] + }, + { + "kind": "outcome", + "text": "Tee tuotteesta 100 % upea ja ilahduttava kaikille sen käyttäjille.", + "label": "vague", + "tags": [ + "fi", + "translation", + "numeric", + "fake-precision", + "meaningless-number" + ] + }, + { + "kind": "outcome", + "text": "Kasvata asiakastyytyväisyyttä 200 % aidosti intuitiivisen käyttöliittymän avulla.", + "label": "vague", + "tags": [ + "fi", + "translation", + "numeric", + "fake-precision", + "unmeasurable-baseline" + ] + }, + { + "kind": "outcome", + "text": "Jonain päivänä jokainen tiimi luottaa taas tiekarttaan.", + "label": "vague", + "tags": [ + "fi", + "translation", + "word-number-trap", + "idiom-one-day" + ] + }, + { + "kind": "outcome", + "text": "Uudesta kojelaudasta tulee yksi tuotteen käytetyimmistä sivuista.", + "label": "vague", + "tags": [ + "fi", + "translation", + "word-number-trap", + "one-of-the" + ] + }, + { + "kind": "outcome", + "text": "Nollakonfiguraation asennus, joka vain toimii kaikille.", + "label": "vague", + "tags": [ + "fi", + "translation", + "word-number-trap", + "hyphenated-zero" + ] + }, + { + "kind": "outcome", + "text": "Tuonti, jossa on päällekkäinen tunniste, hylätään, ja hylkäys nimeää olemassa olevan rivin.", + "label": "good", + "tags": [ + "fi", + "translation", + "refusal", + "names-the-case", + "refusal-names" + ] + }, + { + "kind": "outcome", + "text": "Yhdistäminen hylätään, kunnes jokainen vaadittu arvioija on hyväksynyt muutoksen.", + "label": "good", + "tags": [ + "fi", + "translation", + "refusal", + "refusal-condition" + ] + }, + { + "kind": "outcome", + "text": "Tunniste, joka on myönnetty toiselle työtilalle, hylätään.", + "label": "good", + "tags": [ + "fi", + "translation", + "refusal", + "names-the-case" + ] + }, + { + "kind": "outcome", + "text": "Kassa hylkää vanhentuneen kortin ennen kuin se veloitetaan.", + "label": "good", + "tags": [ + "fi", + "translation", + "refusal", + "refusal-condition", + "active-voice" + ] + }, + { + "kind": "outcome", + "text": "Huonot pyynnöt hylätään.", + "label": "vague", + "tags": [ + "fi", + "translation", + "refusal", + "bare-category", + "genuinely-vague" + ] + }, + { + "kind": "outcome", + "text": "Virheelliset syötteet hylätään.", + "label": "vague", + "tags": [ + "fi", + "translation", + "refusal", + "bare-category", + "genuinely-vague" + ] + }, + { + "kind": "outcome", + "text": "Vaaralliset muutokset estetään.", + "label": "vague", + "tags": [ + "fi", + "translation", + "refusal", + "bare-category", + "blocked-synonym", + "genuinely-vague" + ] + }, + { + "kind": "title", + "text": "Massamuokkaus havaintoaineistolle", + "label": "good", + "tags": [ + "fi", + "translation", + "short-noun-phrase" + ] + }, + { + "kind": "title", + "text": "Lähetyksen peruutus", + "label": "good", + "tags": [ + "fi", + "translation", + "short-noun-phrase", + "two-words" + ] + }, + { + "kind": "title", + "text": "Tumma tila editoriin", + "label": "good", + "tags": [ + "fi", + "translation", + "short-noun-phrase" + ] + }, + { + "kind": "title", + "text": "Uusi ominaisuus", + "label": "vague", + "tags": [ + "fi", + "translation", + "generic-filler" + ] + }, + { + "kind": "title", + "text": "Sekalaiset korjaukset", + "label": "vague", + "tags": [ + "fi", + "translation", + "generic-filler" + ] + }, + { + "kind": "title", + "text": "Erilaisia parannuksia", + "label": "vague", + "tags": [ + "fi", + "translation", + "generic-filler" + ] + }, + { + "kind": "title", + "text": "Nimetön intentti", + "label": "vague", + "tags": [ + "fi", + "translation", + "placeholder" + ] + }, + { + "kind": "constraint", + "text": "Esikatselutila ei saa koskaan lähettää sähköposteja.", + "label": "good", + "tags": [ + "fi", + "translation", + "prohibition" + ] + }, + { + "kind": "constraint", + "text": "Ei henkilötietoja selaimen lokeihin.", + "label": "good", + "tags": [ + "fi", + "translation", + "prohibition", + "short" + ] + }, + { + "kind": "constraint", + "text": "Vasteaika pysyy alle 200 ms:n.", + "label": "good", + "tags": [ + "fi", + "translation", + "bound", + "numeric" + ] + }, + { + "kind": "constraint", + "text": "GDPR-vaatimukset koskevat kaikkea EU-liikennettä ja niitä noudatetaan.", + "label": "good", + "tags": [ + "fi", + "translation", + "obligation" + ] + }, + { + "kind": "constraint", + "text": "Ei muutoksia API:in.", + "label": "good", + "tags": [ + "fi", + "native", + "prohibition", + "short" + ] + }, + { + "kind": "constraint", + "text": "nopea", + "label": "vague", + "tags": [ + "fi", + "translation", + "adjective" + ] + }, + { + "kind": "constraint", + "text": "turvallinen", + "label": "vague", + "tags": [ + "fi", + "translation", + "adjective" + ] + }, + { + "kind": "constraint", + "text": "käyttäjäystävällinen", + "label": "vague", + "tags": [ + "fi", + "translation", + "adjective" + ] + }, + { + "kind": "constraint", + "text": "toimii hyvin", + "label": "vague", + "tags": [ + "fi", + "translation", + "adjective" + ] + }, + { + "kind": "verification", + "text": "Aja tuonti kahdesti ja varmista, ettei tietueita monistu.", + "label": "good", + "tags": [ + "fi", + "translation", + "runnable" + ] + }, + { + "kind": "verification", + "text": "Vie hanke, jonka yhdeltä EIR-riviltä puuttuu PIR-linkki, ja varmista, että vienti estetään ja virhe nimeää rivin.", + "label": "good", + "tags": [ + "fi", + "native", + "rytv-probe-2026-09-27", + "runnable" + ] + }, + { + "kind": "verification", + "text": "testaa se", + "label": "vague", + "tags": [ + "fi", + "translation", + "gesture" + ] + }, + { + "kind": "verification", + "text": "manuaalinen testaus", + "label": "vague", + "tags": [ + "fi", + "translation", + "gesture" + ] } ]