@haus-tech/haus-workflow 4.8.0 → 5.0.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 (29) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/cli.js +1883 -902
  3. package/docs/before-you-start.md +57 -0
  4. package/docs/glossary.md +56 -0
  5. package/library/catalog/manifest.json +97 -34
  6. package/library/global/agents/request-assessor.md +2 -2
  7. package/library/global/agents/request-scorer.md +14 -9
  8. package/library/global/agents/request-specifier-eskalerad.md +1 -1
  9. package/library/global/agents/request-specifier.md +1 -1
  10. package/library/global/skills/haus-workflow/references/new.md +18 -11
  11. package/library/global/skills/request/SKILL.md +731 -127
  12. package/library/global/skills/request/scripts/archive.mjs +1376 -0
  13. package/library/global/skills/request/scripts/check-lasta.mjs +64 -0
  14. package/library/global/skills/request/scripts/compare-scores.mjs +73 -15
  15. package/library/global/skills/request/scripts/contract-check.mjs +110 -49
  16. package/library/global/skills/request/scripts/lib.mjs +81 -0
  17. package/library/global/skills/request/scripts/precheck.mjs +30 -27
  18. package/library/global/skills/request/scripts/report.mjs +571 -0
  19. package/library/global/skills/request/scripts/resolve-anchors.mjs +243 -0
  20. package/library/global/skills/request-assess-impact/SKILL.md +10 -10
  21. package/library/global/skills/request-score/SKILL.md +19 -10
  22. package/library/global/skills/request-sync/SKILL.md +4 -4
  23. package/library/global/skills/request-write-spec/SKILL.md +6 -6
  24. package/library/global/skills/request-write-spec/references/specformat.md +2 -2
  25. package/package.json +5 -2
  26. package/library/global/agents/request-classifier.md +0 -55
  27. package/library/global/skills/request/scripts/compare-classifications.mjs +0 -94
  28. package/library/global/skills/request-classify/SKILL.md +0 -79
  29. package/library/global/skills/request-classify/references/klassningsinstruktion.md +0 -152
@@ -1,27 +1,29 @@
1
1
  ---
2
2
  name: request
3
- description: Enda ingången till Haus requestflöde. Använd när användaren säger "/request", "kör requestflödet", "speca och bedöm", "prisa den här posten", "ta önskemålet hela vägen", "vad ligger i kön", "detaljer <post>", eller när flera request-skills ska kedjas i en följd. Enskilda request-skills (request-write-spec, request-classify m.fl.) fungerar fortfarande fristående; den här skillen äger kedjan, arkivet och dispatchen till subagenter.
3
+ description: Enda ingången till Haus requestflöde. Använd när användaren säger "/request", "kör requestflödet", "speca och bedöm", "prisa den här posten", "ta önskemålet hela vägen", "vad ligger i kön", "detaljer <post>", eller när flera request-skills ska kedjas i en följd. Enskilda request-skills (request-write-spec, request-score m.fl.) fungerar fortfarande fristående; den här skillen äger kedjan, arkivet och dispatchen till subagenter.
4
4
  ---
5
5
 
6
6
  # /request — requestflödets ingång
7
7
 
8
- Tunn orkestrering ovanpå de fem request-skillsen. Den här skillen innehåller
9
- **noll domänregler**: allt om specformat, klassning, poäng och Asana-fält bor i
8
+ Tunn orkestrering ovanpå de fyra request-skillsen. Den här skillen innehåller
9
+ **noll domänregler**: allt om specformat, poäng och Asana-fält bor i
10
10
  respektive skill. Här bor bara routing, kontextbärande, arkivet och dispatchen.
11
11
 
12
12
  ## Uppgifter
13
13
 
14
14
  `/request [uppgift]` — utan argument: visa menyn nedan.
15
15
 
16
- | Uppgift | Gör |
17
- | -------------------------------------- | ---------------------------------------------------------------------------------------- |
18
- | `speca <önskemål eller gid>` | Källmanifest → dispatcha `request-specifier` → arkivera spec + granskarblock |
19
- | `bedöm <gid, slug eller nr>` | Grind → säkra analys → dispatcha `request-classifier` ∥ `request-scorer` → arkivera JSON |
20
- | `status` | Kö-läge via request-sync, BLUF-tabell med radnummer |
21
- | `godkänn <nr eller gid>` | Flytta Offer status framåt via request-sync — se grindavsnittet |
22
- | `underkänn <nr eller gid> <skäl>` | Skäl som Asana-kommentar + iterationsräknare via request-sync |
23
- | `detaljer <gid, slug eller nr> [steg]` | Skriv ut arkiverad JSON/dokument för posten |
24
- | `hjälp` | Förklara uppgifterna, rör inga filer |
16
+ | Uppgift | Gör |
17
+ | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
18
+ | `speca <önskemål eller gid>` | Källmanifest → dispatcha `request-specifier` → arkivera spec + granskarblock |
19
+ | `bedöm <gid, slug eller nr>` | Grind → säkra analys → dispatcha `request-scorer` → arkivera JSON |
20
+ | `status` | Kö-läge via request-sync, BLUF-tabell med radnummer |
21
+ | `godkänn <nr eller gid>` | Flytta Offer status framåt via request-sync — se grindavsnittet |
22
+ | `underkänn <nr eller gid> <skäl>` | Skäl som Asana-kommentar + iterationsräknare via request-sync |
23
+ | `döm <post>` | Människodom vid OLIKA: välj körning, beställ omkörning eller underkänn underlaget |
24
+ | `detaljer <gid, slug eller nr> [steg]` | Skriv ut arkiverad JSON/dokument för posten |
25
+ | `utvärdera <post…> <modell…>` | Kör flödet en gång per namngiven modell mot experimentytan, minst en modell; `sammanställ` ger svepvyn, `rapport` en läsbar jämförelse |
26
+ | `hjälp` | Förklara uppgifterna, rör inga filer |
25
27
 
26
28
  ## Hårda regler
27
29
 
@@ -31,25 +33,46 @@ respektive skill. Här bor bara routing, kontextbärande, arkivet och dispatchen
31
33
  Skill-verktyget (`request-sync`).
32
34
  - **En skrivare i taget mot Asana.** Batchresultat skrivs tillbaka sekventiellt,
33
35
  en post i taget, aldrig parallellt.
34
- - **Dokument klistras bytetroget** in i subagent-prompter. Klassningens och
35
- poängens belägg är ordagranna citat; en trunkerad eller omformaterad inklistring
36
- ger tyst fel svar. Klistra aldrig in en sammanfattning.
37
- - **Prislistan och repot hålls borta från classifier/scorer.** Deras
38
- agentdefinitioner saknar sök- och shellverktyg (Grep/Glob/Bash), men Read
39
- finns kvar för skillens referensfil — läs-scopet är alltså en promptregel,
36
+ - **Dokument klistras bytetroget** in i specifier- och assessor-prompter
37
+ (önskemål, spec, granskarblock). En trunkerad eller omformaterad
38
+ inklistring flyttar källtexten under agentens fötter och ger tyst fel
39
+ svar. Klistra aldrig in en sammanfattning.
40
+ - **Prissättningens underlag pekas ut per sökväg (ADR-0099).** Scorerns
41
+ prompt bär två namngivna absoluta sökvägar (`spec.md`, `impact.md`) i
42
+ stället för inklistrad text — agenten läser filerna själv via Read, så
43
+ ingen avskrift kan trunkeras, och beläggens ankare (källa + delsträng)
44
+ löses av scripten mot exakt samma filer på disk. Läs-scopet är EXAKT
45
+ dessa två filer plus skillens egen katalog, aldrig
46
+ körningskatalogen som helhet: `granskarblock.md` är assessorns underlag
47
+ och äldre pricing-JSON vore ren kontaminering av dubbelkörningens
48
+ oberoende.
49
+ - **Prislistan och repot hålls borta från scorern.** Dess
50
+ agentdefinition saknar sök- och shellverktyg (Grep/Glob/Bash), och Read
51
+ är promptbegränsad enligt regeln ovan — läs-scopet är en promptregel,
40
52
  inte en fysisk spärr. Därför: lägg aldrig prislistans innehåll i prompten,
41
53
  och lägg inga egna sökvägar i dispatcherns rader utöver etiketterna i mallen.
42
- Sökvägar som redan står i det bytetrogna underlaget (analysen namnger filer,
54
+ Sökvägar som redan står i underlaget (analysen namnger filer,
43
55
  det är dess jobb) är inte ett brott — regeln gäller det dispatchern
44
56
  lägger till. Granska `LÄSTA:`-raden i svaret (se dispatch-kontraktet).
45
57
  - **Granskarblocken når aldrig kunden.** Specens granskarblock arkiveras separat
46
58
  (`granskarblock.md`). Analysens "## Till granskaren"-block ligger kvar i
47
- `impact.md` (det är del av underlaget för klassningen) — men inget
59
+ `impact.md` (det är del av underlaget för poängsättningen) — men inget
48
60
  granskarblock får någonsin skickas med i material som går till request-sync
49
61
  för kundsynliga fält.
50
62
  - **Arkivet är cache, Asana är sanningen.** Vid konflikt vinner brädan.
51
63
  - **Godkännande antas aldrig.** Att en spec finns i arkivet betyder inte att den
52
64
  är granskad. Se grinden under `bedöm`.
65
+ - **Experimentdata når aldrig Asana.** `utvärdera` skriver aldrig via
66
+ request-sync; dess enda Asana-kontakt är läsande hämtning av önskemålet i
67
+ huvudkonversationen. `experiments/`-trädet är inte postkataloger: sync,
68
+ batch och guard läser bara `.haus/requests/<gid-eller-slug>/` på toppnivån
69
+ och rör aldrig `experiments/`.
70
+ - **Maskinen kör om enbart vid scriptbevisat kontraktsbrott.** Omdispatch
71
+ (budget EN per agent) svarar på ett fel som precheck, resolve-anchors eller
72
+ contract-check har bevisat. En oenighet mellan två GILTIGA körningar är en
73
+ bedömningsfråga och går alltid till människan via uppgiften `döm`: fler
74
+ maskinkörningar producerar fler röster, aldrig mer sanning. Ingen ny
75
+ omkörningsmekanism får läggas till som bryter mot detta.
53
76
 
54
77
  ## Arkivet: `.haus/requests/`
55
78
 
@@ -57,8 +80,8 @@ Orkestreringens kuvert. De haus-hanterade request-skillsen känner inte till det
57
80
  den här skillen (och agenterna via sina prompter) underhåller det.
58
81
 
59
82
  **En katalog = en spec = en prissättningsbar post.** En bunt (flera specar ur
60
- samma önskemål) blir flera kataloger — klassningen räknar omfattning per spec
61
- och poängen sätts per post, så en katalog med flera specar går inte att bedöma.
83
+ samma önskemål) blir flera kataloger — poängen sätts per post, så en katalog
84
+ med flera specar går inte att bedöma.
62
85
 
63
86
  ```
64
87
  <repo-rot>/.haus/requests/<gid-eller-slug>/
@@ -67,13 +90,22 @@ och poängen sätts per post, så en katalog med flera specar går inte att bed
67
90
  │ kopieras till varje delkatalog)
68
91
  ├── impact.md påverkansanalysen, ordagrann inkl. sitt
69
92
  │ Till granskaren-block
70
- ├── classification-pricing-vN.json nytt varv = ny fil, skriv aldrig över
71
- ├── score-pricing-vN.json N är gemensamt för paret (se bedöm steg 8)
72
- ├── classification-guard-vN.json guard-utfall synkas aldrig, bara arkiveras
73
- ├── score-guard-vN.json
93
+ ├── score-pricing-vN.json nytt varv = ny fil, skriv aldrig över
94
+ ├── score-guard-vN.json guard-utfall synkas aldrig, bara arkiveras
95
+ │ (egen N-sekvens per typ, skild från pricing)
74
96
  └── state.json
75
97
  ```
76
98
 
99
+ Äldre arkiv (före ADR-0098) bär även `classification-pricing-vN.json` /
100
+ `classification-guard-vN.json` från den borttagna klassaxeln. De läses och
101
+ visas som förut (`detaljer`, rapporten, `archive.mjs`) men skrivs aldrig av
102
+ nya körningar.
103
+
104
+ Förkastade försök bär `-avvisad`-suffix och står utanför N-räkningen:
105
+ prissättningens `<typ>-pricing-vN-avvisad.json` och textstegens
106
+ `spec-avvisad-<datum>.md` / `impact-avvisad-<datum>.md` (ADR-0099) — se
107
+ omdispatch-regeln i dispatch-kontraktet.
108
+
77
109
  - Katalognamn: Asana-gid när posten finns på brädan, annars en kebab-slug av
78
110
  spec-rubriken. Bunt utan gid: `<slug>-1`, `<slug>-2`, … När gid myntas senare:
79
111
  döp om katalogen till gid och notera sluggen i `state.json.former_slug`.
@@ -94,6 +126,7 @@ och poängen sätts per post, så en katalog med flera specar går inte att bed
94
126
  "bundle": { "parent_slug": null, "part": null, "of": null },
95
127
  "split": { "parent": null, "part": null, "of": null },
96
128
  "approval": null,
129
+ "granskarutslag": [],
97
130
  "note": null,
98
131
  "runs": []
99
132
  }
@@ -106,14 +139,44 @@ och poängen sätts per post, så en katalog med flera specar går inte att bed
106
139
  `{ "source": "asana" | "manual", "by": "<namn, krävs vid manual>",
107
140
  "status_read": "<värde>", "date": "YYYY-MM-DD" }`.
108
141
  Anteckningen är spårbarhet, aldrig själva godkännandet.
142
+ - `granskarutslag` är en lista, en rad per dom, skriven via `archive.mjs utslag`, aldrig för hand.
143
+ Radform: `{ "typ": "viktning" | "kalibrering" | "sakandring", "date": "YYYY-MM-DD", "beslutad_av": "<namn>", ... }`.
144
+ Viktning bär dessutom `beslut` (ordagrant), `axel` (`score`; `classification`
145
+ förekommer i äldre arkiv), `n` och `galler_fil`: raden ÄR spärrlyftet,
146
+ `archive.mjs sparr` läser den.
147
+ Kalibrering bär `fraga`, `lasning1`, `lasning2`: en notis om att en
148
+ protokollfråga lät två giltiga läsningar divergera, underlag för nästa
149
+ instruktionsversion. Sakändring bär `beslut`: kvittot i `state.json` för
150
+ samma händelse som citatblocket i `impact.md` (se `döm`, vägen "Underlaget
151
+ är fel"). Det ordagranna beslutet bor i citatblocket, raden här är bara
152
+ revisionsspåret, aldrig underlaget självt.
153
+ Listan läses medvetet ALDRIG tillbaka in i någon prompt, oavsett typ: ett
154
+ utslag som når agenterna styr nästa bedömning i stället för att dokumentera
155
+ den föregående. Nollas (töms) vid respec, av samma skäl som `approval`
156
+ nollas (se `speca` steg 5): ett utslag mot en ersatt spec är inte längre
157
+ giltigt. Bor ENDAST i `state.json`. Rader skrivs aldrig om.
109
158
  - `runs[]` får en rad per dispatch, även spec- och analyskörningar:
110
159
  `{ "file": "spec.md" | "impact.md" | "<run>.json", "skill": "...",
111
- "agent": "...", "model": "<ur MODELL-raden>", "effort": "<ur agentdefinitionen>",
160
+ "agent": "...",
161
+ "model": { "uppgiven": "<ur MODELL-svansraden>", "fakturerad": "<känd fakturerad modell, annars null>" },
162
+ "effort": "<ur agentdefinitionen>",
112
163
  "date": "YYYY-MM-DD",
113
164
  "reference_sha256": { "<referensfil>": "<sha256>" },
114
165
  "underlag_sha256": { "spec.md": "<sha256>", "impact.md": "<sha256>" },
115
- "double_run": "bekräftad" | "olika" | null,
116
- "superseded_by": null }`.
166
+ "double_run": "bekräftad" | "olika" | "tolerans" | null,
167
+ "superseded_by": null, "partial_rerun": false, "pairs_with": null }`.
168
+ `model` bär två fält (ADR-0099): `uppgiven` är agentens självrapport ur
169
+ MODELL-svansraden, `fakturerad` fylls när dispatchern känner den
170
+ fakturerade modellen (`add-run --fakturerad`), annars `null` — faktureringens
171
+ sanning per körning bor i `proveniens.fakturerade_modeller`. Äldre arkiv bär
172
+ `model` som ren sträng och läses som förut.
173
+ `quote_repairs` förekommer i äldre arkiv (före ADR-0092) och läses men
174
+ skrivs aldrig av nya körningar.
175
+ Stämplingen görs alltid via `archive.mjs add-run`; hur N räknas ut, vilket
176
+ suffix en fil får och hur en riktad omkörnings parning skrivs bor i
177
+ scriptet och testerna. VILKA `--typ` som skickas till `archive.mjs next-n`,
178
+ alltså vilka axlar som faktiskt kördes i varvet, är fortfarande
179
+ orkestratorns beslut, inte scriptets.
117
180
  `underlag_sha256` är underlagsproveniensen: hashen för de dokument körningen
118
181
  faktiskt såg. Precheck vägrar bedöma när analysens spec-hash inte matchar
119
182
  nuvarande `spec.md`, och guard ska vägra diffa mot en körning vars underlag
@@ -125,12 +188,75 @@ och poängen sätts per post, så en katalog med flera specar går inte att bed
125
188
  alla arkiverade körningar. Samma verktyg används för `underlag_sha256`.
126
189
  Referensfilerna ligger under Claude-konfigkatalogen (`CLAUDE_CONFIG_DIR`
127
190
  eller `~/.claude`). En senare guard-körning jämför hash och vägrar diffa vid
128
- skevhet ("omklassning krävs, skalan har ändrats").
191
+ skevhet ("ompoängsättning krävs, skalan har ändrats").
129
192
  - `status` blir ALDRIG `priced` när något stopputfall finns i varvets
130
- körningar, eller när dubbelkörningen gav OLIKA (`double_run: "olika"`).
193
+ körningar, eller när den GÄLLANDE raden har `double_run: "olika"` olyft, eller
194
+ när den gällande raden bär `partial_rerun: true` utan en matchande
195
+ dom (en beställd omkörning väntar på granskaren, se uppgiften `döm`).
196
+ `archive.mjs sparr` avgör bägge skälen identiskt (`sparrSkal`).
197
+ Spärren lyfts av ett viktningsutslag (`archive.mjs sparr` är den enda tolken,
198
+ se uppgiften `döm`) eller av en beställd omkörning vars dom landat i ett
199
+ viktningsutslag; ingen annan väg lyfter den, och den oeniga axelns äldre
200
+ rader skrivs aldrig om även efter lyft.
201
+ `double_run: "tolerans"` räknas som bekräftad och blockerar inte `priced`.
202
+ I äldre arkiv som bär en klassaxel (före ADR-0098) vägrar
203
+ `archive.mjs set-status priced` därutöver ett OFULLSTÄNDIGT par:
204
+ klass- och poängaxelns gällande varvnummer skiljer sig utan att skillnaden
205
+ är en förklarad enaxlig omkörning (`partial_rerun: true` på den högre
206
+ axelns gällande rad), eller poängaxeln saknas helt. Ett arkiv utan
207
+ klassaxel prövas aldrig mot par-regeln — poängen är den enda axeln.
208
+ Det här skälet prövas av `pricedBlockers` (`archive.mjs set-status`),
209
+ inte av `archive.mjs sparr` — reglerna bor i scriptet och dess tester.
131
210
  - `spec_synced` sätts av speca steg 7 (posten lagd på brädan);
132
211
  `guard_run` reserveras för guard-kommandot (senare byggsteg).
133
212
 
213
+ ### Experimentytan: `experiments/`
214
+
215
+ Modellkörningar från `utvärdera` bor i ett eget träd och är aldrig prisposter:
216
+
217
+ ```
218
+ <repo-rot>/.haus/requests/experiments/<slug>/
219
+ ├── experiment.json ticketreferens, datum, modellista, manifest-hash,
220
+ │ status per körning
221
+ ├── <modell>/ `default` som modellvärde ger katalognamnet
222
+ │ `baseline/`
223
+ │ ├── k1/
224
+ │ ├── k1/korning.json headless-svaret RÅTT: USD, tokens, dispatcher
225
+ │ ├── k1/matning.json startarens egen mätning: startad, avslutad, wall_ms
226
+ │ ├── k2/ senare körning av SAMMA modell
227
+ │ └── k2/del-1/ … delkataloger vid DELA_UPP
228
+ └── <annan modell>/k1/ …
229
+ ```
230
+
231
+ - **Två filer, två ägare.** `korning.json` är headless-svaret ordagrant och
232
+ ägs av CLI:n; `matning.json` är startarens klocka och ägs av oss. En
233
+ haus-nyckel inne i `korning.json` skulle göra den till ett blandat format
234
+ som inte längre går att ställa mot CLI:ns egen utdata.
235
+ - **Ingen körning skrivs någonsin över.** Värdet i en gammal körning är att
236
+ kunna jämföra samma modell före och efter en ändring i skillen eller
237
+ agentprompterna; skriver en ny körning över den är den jämförelsen borta för
238
+ alltid. Varje körning får därför en egen katalog `k<N>` under sin modell.
239
+ - **Körningskatalogen ÄR postkatalogen.** Den följer arkivkontraktet
240
+ bytetroget (spec.md, granskarblock.md, impact.md, pricing-JSON, state.json,
241
+ `runs[]`-reglerna), så `precheck.mjs` och `contract-check.mjs` fungerar
242
+ oförändrade, de pekas bara på `<modell>/k<N>/`. Proveniensen följer med gratis.
243
+ - **Numret allokeras aldrig av skillen själv**, alltid med
244
+ `archive.mjs ny-korning <modellkatalog>`, som skapar katalogen
245
+ atomiskt och skriver dess sökväg. Två samtidiga processer, till exempel två
246
+ Claude-konton mot samma checkout, får då `k2` och `k3`, aldrig samma katalog.
247
+ Räknar skillen själv förlorar den ena processens körning tyst.
248
+ - `state.json` i en körningskatalog bär markören `"experiment": true`.
249
+ `approval` förblir `null` och `status` blir ALDRIG `priced`: ett
250
+ experimentresultat kan inte förväxlas med en prispost.
251
+ - Katalognamnet `experiments` är reserverat: en spec vars rubrik skulle sluggas
252
+ till `experiments` får suffixet `-post`.
253
+ - `experiment.json` bär `varianter: []` i deklarationsordning och `status` per
254
+ körning: `"klar"`, `"ofärdig"`, `"stopp"`, `"väntar på dom"`. Literalerna
255
+ skrivs exakt så, mellanslag och diakritiska tecken inkluderade. Äldre filer
256
+ med `baselinemodell` och `kandidatmodell` läses utan fel; nya skrivningar
257
+ använder bara `varianter`.
258
+ - `experiments/` ärver gitignore-skyddet från `.haus/requests/`.
259
+
134
260
  ## Uppgift: `speca`
135
261
 
136
262
  1. **Underlag.** Rått önskemål inklistrat, eller ett gid — hämta då posten via
@@ -154,16 +280,31 @@ och poängen sätts per post, så en katalog med flera specar går inte att bed
154
280
  kopieras till varje katalog som `granskarblock.md`). Bunt ur en gid-post:
155
281
  del 1 behåller gid-katalogen, resterande delar får slug-kataloger med
156
282
  `bundle.parent_slug` satt till gid:t; syskontasks på brädan mintas först
157
- vid sync. Lägg en `runs`-rad (`file: "spec.md"`, med `underlag_sha256`
158
- tomt) i varje berörd state.json.
283
+ vid sync. Stämpla varje berörd `state.json` med
284
+ `node <skillkatalog>/scripts/archive.mjs add-run <katalog> --file spec.md
285
+ --skill request-write-spec --agent <request-specifier|request-specifier-eskalerad>
286
+ --model <ur MODELL-svansraden> --ref references/specformat.md=<hash via ref-hash.mjs
287
+ på request-write-spec/references/specformat.md>`. Scriptet sätter
288
+ `underlag_sha256` tomt, en spec har inget underlag i repot — men
289
+ referens-hashen måste sättas explicit precis som för poängen, annars
290
+ tappas specformatets proveniens (se runs-kontraktet).
159
291
  **Respec över befintlig katalog invaliderar analysen:** när `spec.md`
160
292
  ersätts döps ev. `impact.md` om till `impact-superseded-<datum>.md`,
161
- status sätts till `spec_draft` och `approval` nollas — en ny spec är
162
- ogranskad per definition, och en gammal analys mot ny spec är exakt den
163
- skevhet proveniens-kontrollen finns för att stoppa.
293
+ status sätts till `spec_draft`, `approval` nollas och `granskarutslag`
294
+ töms — en ny spec är ogranskad per definition, ett utslag mot en ersatt
295
+ spec är inte längre giltigt, och en gammal analys mot ny spec är exakt
296
+ den skevhet proveniens-kontrollen finns för att stoppa.
164
297
  6. **Formkontroll.** Kör `node <skillkatalog>/scripts/precheck.mjs <katalog>`
165
298
  per katalog. BRISTER i spec/granskarblock → dispatcha om specifiern EN gång
166
- enligt omdispatch-regeln; BRISTER i `state.json` är wrapperns egna filer och
299
+ enligt omdispatch-regeln. **Det förkastade utkastet får kvitto
300
+ (ADR-0099, samma regel som prissättningens `-avvisad`):** arkivera det
301
+ som `spec-avvisad-<datum>.md` innan omdispatchen, och stämpla det med en
302
+ egen runs-rad via `archive.mjs add-run --file spec-avvisad-<datum>.md
303
+ --note "<kontrollens felrader>"` (övriga flaggor som spec-radens). VARJE
304
+ dispatch får sin egen runs-rad — även den förkastade — så arkivet bär
305
+ både att ett omtag skedde och varför; en spec godkänd i andra försöket
306
+ får aldrig se ut som en som blev rätt direkt.
307
+ BRISTER i `state.json` är wrapperns egna filer och
167
308
  rättas av wrappern själv, aldrig via omdispatch. Kvarstår brister, visa dem
168
309
  och stanna. VARNING:ar visas men stoppar inget.
169
310
  7. **Svara BLUF:** en rad per spec (rubrik + ev. flaggor), sedan granskarblocket
@@ -174,8 +315,8 @@ och poängen sätts per post, så en katalog med flera specar går inte att bed
174
315
 
175
316
  ## Uppgift: `bedöm`
176
317
 
177
- Kör prissättningsbenet: påverkansanalys om den saknas, sedan klass ∥ poäng med
178
- oenighetsgrind.
318
+ Kör prissättningsbenet: påverkansanalys om den saknas, sedan poängsättning med
319
+ driftvakt.
179
320
 
180
321
  1. **Grind — obligatorisk, aldrig överhoppbar.** Bedömning kräver en granskad
181
322
  spec, och godkännandet bor i Asana:
@@ -209,77 +350,110 @@ oenighetsgrind.
209
350
  skickas med som internt underlag (samma förtroendedomän; flaggorna där är
210
351
  ofta analysens viktigaste ingång). Arkivera svaret som `impact.md`
211
352
  (inklusive dess "## Till granskaren"-block — visa blocket för användaren,
212
- aldrig till kundfält), lägg en `runs`-rad (`file: "impact.md"`,
213
- `underlag_sha256` med hash för `spec.md`). Kör precheck igen med `--impact`.
214
- 4. **Prissättning.** Dispatcha `request-classifier` och `request-scorer`
215
- **parallellt i samma meddelande** enligt promptmallarna. Ingen prislista,
216
- inget repo, inga andra posters dokument i prompterna.
217
- 5. **Kontraktskontroll.** Strippa svansraderna (`MODELL:`, `LÄSTA:`, ev.
218
- `AVVIKELSE:`) ur varje svar, skriv den rena JSON:en till en temporär fil och
219
- kör `node <skillkatalog>/scripts/contract-check.mjs <svar.json> <spec.md> <impact.md> --expect-id <katalogens id>`.
220
- BROTT → dispatcha om just den agenten EN gång enligt omdispatch-regeln;
221
- kvarstår brott, visa dem och stanna utan att arkivera det svaret som giltigt
222
- varv — men släng det inte: arkivera det som `<typ>-pricing-vN-avvisad.json`
223
- (utanför N-räkningen) med kontrollens felrader i en runs-rad, så även
224
- misslyckade körningar har kvitto. En `AVVIKELSE:`-rad visas alltid för
225
- användaren.
226
- 6. **Dubbelkörning (driftvakt).** Körs BARA när inget av svaren är ett
227
- stopputfall — stopproutningen i steg 7 går alltid först, en post som ändå
228
- går till människa ska inte kosta fyra extra körningar. Trigger: klassningens
229
- **utfall** är MEDIUM, LARGE eller OFFERT, ELLER poängen är ≥ 4 (poängen
230
- multipliceras direkt till pris — en hög poäng på låg klass förtjänar samma
231
- vakt). Dispatcha då `request-classifier` OCH `request-scorer` EN gång till,
232
- samma prompter, nya isolerade körningar, parallellt i samma meddelande.
233
- Kostnaden (ett extra par körningar) läggs medvetet exakt där pengarna står
234
- på spel — poängen multipliceras direkt till pris och båda skillsens eget
353
+ aldrig till kundfält), stämpla med
354
+ `node <skillkatalog>/scripts/archive.mjs add-run <katalog> --file impact.md
355
+ --skill request-assess-impact --agent request-assessor
356
+ --model <ur MODELL-svansraden>` (scriptet sätter `underlag_sha256` mot
357
+ `spec.md`). Kör precheck igen med `--impact`. Fäller precheck det nya
358
+ utkastet → omdispatcha assessorn EN gång enligt omdispatch-regeln, och ge
359
+ det förkastade utkastet kvitto precis som i speca steg 6: arkivera det som
360
+ `impact-avvisad-<datum>.md` och stämpla en egen runs-rad med kontrollens
361
+ felrader i `--note` (ADR-0099).
362
+ 4. **Prissättning.** Dispatcha `request-scorer` enligt promptmallen.
363
+ Ingen prislista, inget repo, inga andra posters dokument i prompten.
364
+ 5. **Ankarlösning och kontraktskontroll.** Strippa svansraderna (`MODELL:`,
365
+ `LÄSTA:`, ev. `AVVIKELSE:`) ur svaret. Kör
366
+ `node <skillkatalog>/scripts/check-lasta.mjs "<LÄSTA-radens innehåll>" <spec.md> <impact.md>`
367
+ (exit 1 = kontraktsbrott, se dispatch-kontraktet). Skriv sedan den rena
368
+ JSON:en till fil och kör i ordning:
369
+ `node <skillkatalog>/scripts/resolve-anchors.mjs <svar.json> <spec.md> <impact.md>`
370
+ (löser beläggsankare till ordagranna källblock; OLÖST/FLERTYDIG är ett
371
+ kontraktsbrott), sedan
372
+ `node <skillkatalog>/scripts/contract-check.mjs <svar.json> <spec.md> <impact.md> --expect-id <katalogens id>`.
373
+ BROTT från något av scripten → dispatcha om scorern EN gång enligt
374
+ omdispatch-regeln, med felraderna i prompten. Kvarstår brott: visa dem och
375
+ stanna utan att arkivera svaret som giltigt varv, men släng det inte:
376
+ arkivera som `score-pricing-vN-avvisad.json` (utanför N-räkningen) med
377
+ kontrollens felrader i en runs-rad, så även misslyckade körningar har
378
+ kvitto. En `AVVIKELSE:`-rad visas alltid för användaren.
379
+ 6. **Dubbelkörning (driftvakt).** Körs BARA när svaret inte är ett
380
+ stopputfall — stopproutningen nedan går alltid först, en post som ändå
381
+ går till människa ska inte kosta extra körningar. Trigger: poängen är ≥ 1
382
+ (ADR-0098: tröskeln täcker minst det den borttagna klassaxelns
383
+ MEDIUM/LARGE/OFFERT-villkor täckte; poängen multipliceras direkt till
384
+ pris). Dispatcha då `request-scorer` EN gång till,
385
+ samma prompt, ny isolerad körning.
386
+ Kostnaden (en extra körning) läggs medvetet exakt där pengarna står
387
+ på spel — poängen multipliceras direkt till pris och skillens eget
235
388
  acceptanstest är att två oberoende körningar ger samma resultat.
236
- - Kör kontraktskontrollen (steg 5) på båda nya svaren först, med egen
237
- omdispatch-budget (EN per agent).
389
+ - Kör hela steg 5 (ankarlösning, sedan kontraktskontroll) på det nya
390
+ svaret först, med egen omdispatch-budget (EN).
238
391
  - Jämför sedan deterministiskt:
239
- `node <skillkatalog>/scripts/compare-classifications.mjs <klass1.json> <klass2.json>`
240
392
  `node <skillkatalog>/scripts/compare-scores.mjs <poäng1.json> <poäng2.json>`
241
- - **Båda LIKA** → körning 1-paret gäller: `double_run: "bekräftad"` på dess
242
- runs-rader. Körning 2-filerna arkiveras som `...-pricing-vN-run2.json`
243
- med SAMMA N som paret och `superseded_by` satt till motsvarande körning
244
- 1-fil. `-run2`-filer räknas ALDRIG när nästa N bestäms, och guard och
393
+ - **LIKA** → körning 1 gäller: `double_run: "bekräftad"` på dess
394
+ runs-rad. Körning 2-filen arkiveras som `score-pricing-vN-run2.json`
395
+ med SAMMA N och `superseded_by` satt till körning 1-filen.
396
+ `-run2`-filer räknas ALDRIG när nästa N bestäms, och guard och
245
397
  `detaljer` läser alltid filen utan suffix.
246
- - **Någon OLIKA** → behandla posten som ESKALERA. Det beskriver routingen,
247
- aldrig artefakten: ingen fil ändras, ingen JSON skrivs om, inget
248
- ESKALERA-värde patchas in. Arkivera båda paren (körning 2 som `-run2`),
249
- sätt `double_run: "olika"` och `superseded_by: null` på alla berörda
250
- runs-rader, status `assessed`. Visa diffen (rad · körning 1 · körning 2)
251
- för granskaren. Inget `customer`-block ur någon av körningarna får gå
252
- till request-sync så länge `double_run` står på `"olika"`. Välj ALDRIG
253
- en av körningarna själv och räkna ALDRIG fram ett medelvärde.
254
- **Återställningen är ett nytt varv:** efter granskarens beslut (justera
255
- underlaget, eller konstatera att en körning hade rätt) körs `bedöm` om —
256
- spärren sitter på varvets rader, ett nytt varv med LIKA låser upp.
257
- - Exit 2 från ett jämförelsescript är ett dispatcherfel (ojämförbara
398
+ - **OLIKA** → domen är människans, aldrig maskinens. Arkivera båda
399
+ körningarna (körning 1 utan suffix, körning 2 som `-run2`; på båda
400
+ raderna `double_run: "olika"` med `superseded_by: null`).
401
+ Status `assessed`. Ingen kundtext får gå till
402
+ request-sync medan spärren står (`archive.mjs sparr` är tolken).
403
+ Visa i BLUF-svaret beläggen bakom oenigheten, sida vid sida:
404
+ diffraderna (outcome, score, ankare, konfidens) har ingen
405
+ radkoppling till beläggen, `drivers[]` är en egen lista, så visa
406
+ samtliga `drivers[].citat`/`pastaende` ur båda körningarna
407
+ som stöd för hela utfallet. Det var beläggen som avgjorde domen i
408
+ varje uppmätt äkta oenighet. Hänvisa sedan till `döm <post>` och
409
+ STANNA: ingen omkörning, ingen eskalering, inget val av körning.
410
+ - **LIKA_TOLERANS** (poängskillnad ≤ 0,5 med samma outcome, oavsett
411
+ konfidens, en konfidensskillnad ensam fäller aldrig ett par inom
412
+ toleransen) → paret räknas som bekräftat. Verdiktradens utpekade
413
+ körning (högsta poängen) blir huvudfilen `score-pricing-vN.json`,
414
+ och den andra arkiveras som `-run2` med `superseded_by` satt mot
415
+ huvudfilen, oavsett vilken av dem som var körning 1. Det är
416
+ avsiktligt: båda körningarna har passerat kontraktskontrollen och
417
+ skillnaden ligger inom toleransen. Sätt `double_run: "tolerans"` på
418
+ bägge runs-raderna. Toleransen är en medveten prisregel: högsta
419
+ värdet skyddar marginalen, och ett medelvärde räknas fortfarande
420
+ aldrig fram. Visa toleransraderna i BLUF-svaret.
421
+ - Exit 2 från jämförelsescriptet är ett dispatcherfel (ojämförbara
258
422
  körningar) — visa felet och stanna, rapportera aldrig LIKA.
259
- 7. **Oenighetsgrind klass ↔ poäng.** Deterministiska regler, i ordning:
260
- - Endera svaret är ett stopputfall → hela posten routas enligt stopplistan
261
- nedan. Det andra svaret arkiveras men markeras `superseded_by` mot
262
- stopputfallets fil, och statusen blir `assessed`, aldrig `priced`.
263
- - Båda levererar (klass + poäng): flagga oenighet i BLUF-tabellen mot
264
- ankarbanden — klass TINY/SMALL med poäng ≥ 4, klass MEDIUM med poäng
265
- ≤ 0.75 eller ≥ 13, klass LARGE/OFFERT med poäng ≤ 3. Flaggan är
266
- information till granskaren — ingen automatisk åtgärd, ingen omkörning.
267
- 8. **Arkivera** godkända JSON-svar. Paret arkiveras atomärt: båda eller inget,
268
- och `N` i `-pricing-vN.json` är gemensamt (nästa N = högsta befintliga för
269
- någon av filtyperna + 1; `-run2`- och `-avvisad`-filer räknas aldrig;
270
- guard-filer har en EGEN N-sekvens, skild från pricing). Uppdatera
271
- `state.json.runs` med modell, effort, referens-hash och `underlag_sha256`
272
- (hash för `spec.md` och `impact.md` som körningen såg) per fil, status →
273
- `priced` (eller `assessed` vid stopputfall).
274
- 9. **Svara BLUF:** tabell `post · klass · poäng · utfall · konfidens`, en rad
275
- per avvikelse värd att se (oenighetsflagga, ESKALERA, DELA_UPP,
423
+ 7. **Arkivera** godkända JSON-svar. Hämta varvets N med
424
+ `node <skillkatalog>/scripts/archive.mjs next-n <katalog> --typ score`,
425
+ skriv filerna (`score-pricing-vN.json`, körning 2
426
+ som `-run2`, avvisade som `-avvisad`), och stämpla VARJE körning med
427
+ `archive.mjs add-run` (flaggor för fil, skill, agent, modell ur
428
+ MODELL-svansraden, effort ur agentdefinitionen, referens-hash via `--ref`
429
+ för skillens referensfil (se runs-kontraktet), double-run-utfall,
430
+ superseded-by, partial-rerun och pairs-with där de gäller). Exempel för
431
+ huvudfilen vid ett bekräftat par:
432
+ `node <skillkatalog>/scripts/archive.mjs add-run <katalog>
433
+ --file score-pricing-v1.json --skill request-score
434
+ --agent request-scorer --model <ur MODELL-svansraden>
435
+ --effort <ur agentdefinitionen> --ref poangskala.md=<hash via
436
+ ref-hash.mjs på request-score/references/poangskala.md>
437
+ --double-run bekräftad`. `--double-run` tar ENDAST de tre
438
+ literalerna `bekräftad`, `olika` eller `tolerans` (scriptet vägrar allt
439
+ annat, flaggnamnet med bindestreck, aldrig `--double_run` med understreck
440
+ som i JSON-fältet). Utelämna flaggan helt när ingen dubbelkörning gjorts.
441
+ `--superseded-by <fil>` och `--partial-rerun` (utan värde) läggs till där
442
+ de gäller, se runs-kontraktet. Stämplingsreglerna
443
+ bor i scriptet och dess tester, inte här. Sätt status via
444
+ `archive.mjs set-status <katalog> priced` (scriptet vägrar vid spärr,
445
+ stopputfall och experiment — se runs-kontraktet)
446
+ eller `assessed`.
447
+ 8. **Svara BLUF:** tabell `post · poäng · utfall · konfidens`, en rad
448
+ per avvikelse värd att se (ESKALERA, DELA_UPP,
276
449
  INPUT_SAKNAS, AVVIKELSE-rader). JSON:en visas inte i chatten —
277
450
  `detaljer <post>` gör det. Skriv-tillbaka till brädan sker via request-sync
278
451
  i huvudkonversationen, bara på uttrycklig begäran — och det är den flytten
279
452
  som tar posten ✅ Spec ok → ⚖️ Poängsättning redo för granskning enligt
280
453
  syncens regler.
281
454
 
282
- **Stopputfall routas, aldrig tolkas:**
455
+ **Stopputfall routas, aldrig tolkas.** Ett stopputfall arkiveras som varvets
456
+ fil (steg 7), statusen blir `assessed`, aldrig `priced`, och posten routas:
283
457
 
284
458
  - `DELA_UPP` → föreslå ny analysrunda per del; delarna blir egna kataloger med
285
459
  `split` satt (`part`/`of`), och en del utan tak blir `OVER_SKALAN`, aldrig
@@ -329,18 +503,431 @@ missta:
329
503
  - Verben rör Offer status och kommentarer — aldrig Score, Class eller
330
504
  beskrivningsfält.
331
505
 
506
+ ## Uppgift: `döm`
507
+
508
+ Människans dom över en post vars dubbelkörning skrev OLIKA. Adressering som
509
+ `godkänn` (radnummer eller gid, aldrig fuzzy). Kräver att spärren står
510
+ (`archive.mjs sparr` exit 1), annars: visa status och stanna.
511
+
512
+ **En variantkatalog (från `utvärdera`) adresseras med sin sökväg direkt,
513
+ aldrig med radnummer eller gid.** Experimentets slug är samma sträng som ett
514
+ vanligt gid skulle vara, så gid-adressering skulle resolva till postkatalogen
515
+ på toppnivå, inte till variantkatalogen under `experiments/<slug>/`.
516
+ `archive.mjs` tar ändå en katalog som argument oavsett var den ligger, så
517
+ sökvägen fungerar identiskt.
518
+
519
+ **En dom på en katalog under `experiments/` skriver aldrig om
520
+ `experiment.json` i sig.** `archive.mjs utslag` och `archive.mjs sparr`
521
+ verkar bara på variantkatalogens egen `state.json`. Statusen i
522
+ `experiment.json` uppdateras inte av `döm` — den uppdateras av `utvärdera`,
523
+ körd igen för samma post, vars återupptagbarhetsregel omvärderar en
524
+ `"väntar på dom"`-variant och gör utfallet synligt för rapporten när den körs
525
+ (se `utvärdera`).
526
+
527
+ 1. **Visa underlaget.** Kör jämförelsescriptet på det gällande paret och visa
528
+ varje diffrad med beläggen sida vid sida (som i bedöm steg 6). Ingen
529
+ sammanfattning i stället för beläggen: domen fattas på dem.
530
+ 2. **Fråga granskaren** (AskUserQuestion) med tre vägar.
531
+ - **"Körning N gäller"** (N är 1 eller 2, vilken av de två uppmätta
532
+ körningarna som hade rätt): skriv domen via `archive.mjs utslag <katalog> --typ viktning --beslut "<ordagrant>" --beslutad-av <namn> --axel score --n <n> --galler-fil <fil>`.
533
+ (`--axel classification` finns kvar enbart för domar i äldre arkiv
534
+ med en klassaxel, före ADR-0098.)
535
+ `--n` är INTE körningsnumret i rubriken ovan: det är det AKTUELLA
536
+ varvet, avläst ur den senaste pricing-filen
537
+ `score-pricing-v<n>.json` (aldrig `next-n`s utdata, som pekar på
538
+ NÄSTA, ännu okörda varv). `--galler-fil` är den arkiverade fil
539
+ granskaren dömer till
540
+ förmån för: körning 1 → `score-pricing-v<n>.json` utan suffix,
541
+ körning 2 → `score-pricing-v<n>-run2.json`. Efter en beställd
542
+ omkörning (nästa väg) finns TRE arkiverade filer, och
543
+ `--galler-fil` kan peka på VILKEN som helst av dem: omkörningens egen
544
+ fil (version = gällande `n`), eller endera av de två ursprungliga
545
+ körningarnas filer från ett LÄGRE varv, om granskaren dömer att en av
546
+ dem hade rätt hela tiden. Scriptet vägrar bara ett HÖGRE varv än `--n`
547
+ (en dom kan aldrig gälla ett varv som inte fanns när den skrevs) och en
548
+ fil som inte faktiskt arkiverades via `add-run`. Kör `archive.mjs sparr`
549
+ efteråt: exit 0 betyder att spärren är lyft, exit 1 betyder att
550
+ något fortfarande väntar (utskriften namnger vad).
551
+ Inga filer döps om eller skrivs om.
552
+ - **"Beställ omkörning":** EN ny körning, ingen tvångsdubbling: dispatcha
553
+ scorern (ordinarie promptmall och kontraktskedja), arkivera med
554
+ `partial_rerun` och `pairs_with` mot den stående filen. Den nya
555
+ raden väntar precis som originalraden på en dom (`archive.mjs sparr`
556
+ spärrar den ensam, oavsett vad den råkar bära i `double_run`): visa
557
+ scriptdiffen mot BÅDA de stående körningarna och gå till steg 2 igen,
558
+ det är fortfarande granskaren som dömer. Vill granskaren ha två nya
559
+ mätningar beställs två.
560
+ - **"Underlaget är fel":** granskarens beslut appendas i `impact.md` som
561
+ citatblock enligt mallen nedan, assessorn körs om (ny `impact.md`),
562
+ därefter kör `bedöm` ett nytt fullt varv. En körning mot
563
+ gammalt underlag paras aldrig med en mot nytt.
564
+ 3. **Kalibreringsnotis.** Är oenigheten äkta (en fråga eller ett ankarval som
565
+ två giltiga körningar läste olika, inte en etikett): skriv `archive.mjs utslag <katalog> --typ kalibrering --beslutad-av <namn> --fraga <id> --lasning1 "<körning 1:s läsning, en mening>" --lasning2 "<körning 2:s läsning>"`.
566
+ Notisen är råmaterial till nästa instruktionsversion (mekanismen bakom
567
+ v0.2 till v0.6) och läses aldrig in i någon prompt. `archive.mjs kalibrering <arkivrot>`
568
+ listar alla.
569
+
570
+ **Granskarutslag skrivs som citat, aldrig som prosa.** När granskarens
571
+ beslut ska in i underlaget, alltså när det ändrar eller preciserar en
572
+ sakuppgift i analysen, eller av annat skäl måste stå i underlaget för
573
+ spårbarhet: appendas det sist i `impact.md` som ett avgränsat block enligt
574
+ mallen nedan, beslutet ordagrant citerat, aldrig omformulerat av
575
+ dispatchern och aldrig invävt i analysens löptext. En fritt formulerad
576
+ utslagsmening läses av nästa körning som sakuppgift och styr om
577
+ bedömningen, det var precis felet i pilotkörningarna, därför alltid mallen,
578
+ aldrig fri prosa. Kräver beslutet en faktisk sakändring i analysen: kör om
579
+ assessorn (ny `impact.md`). Underlaget har i båda fallen ändrats, så
580
+ `bedöm` kör ett nytt fullt varv (se steg 2 ovan, vägen "Underlaget är fel").
581
+ Skriv samtidigt kvittot `archive.mjs utslag <katalog> --typ sakandring --beslut "<ordagrant>" --beslutad-av <namn>`
582
+ i `state.json` (se `granskarutslag` ovan): citatet i `impact.md` är
583
+ underlaget, sakandring-raden är bara revisionsspåret. Ett rent
584
+ viktningsutslag, där granskaren bara konstaterar att en körning hade rätt
585
+ utan att röra någon sakuppgift, hör ALDRIG hemma här: det skrivs i stället
586
+ via `archive.mjs utslag --typ viktning` (se steg 2 ovan, vägen "Körning N
587
+ gäller"), just för att underlaget ska förbli oförändrat. Blocket är internt
588
+ underlag med samma status som Till granskaren-blocket och följer aldrig
589
+ med i kundtext.
590
+
591
+ ```
592
+ ## Granskarutslag (varv <N>, <YYYY-MM-DD>)
593
+
594
+ > <granskarens beslut, ordagrant>
595
+
596
+ Utslaget gäller endast viktningen mellan körningarna och ändrar inga
597
+ sakuppgifter i analysen ovan.
598
+ ```
599
+
332
600
  ## Uppgift: `detaljer`
333
601
 
334
602
  `detaljer <gid|slug|nr> [steg]` — skriv ut arkiverade filer för posten: utan
335
603
  steg en lista över vad som finns, med steg (`spec`, `granskarblock`, `analys`,
336
- `klass`, `poäng`, `state`) filens innehåll rått. För `klass`/`poäng`: senaste
604
+ `poäng`, `state`) filens innehåll rått; `klass` läser äldre arkivs
605
+ classification-filer (före ADR-0098). För `klass`/`poäng`: senaste
337
606
  `-pricing-vN.json` utan suffix (aldrig `-run2`/`-avvisad`).
338
607
 
339
- ## Mekaniska kontroller: `scripts/`
608
+ ## Uppgift: `utvärdera`
609
+
610
+ Modellutvärdering: `utvärdera <post…> <modell…>`, med minst en modell. Kör en
611
+ post genom hela flödet en gång per namngiven modell, varje varv med samtliga
612
+ tre agenter (specifiern, assessorn, scorern) tvingade till varvets
613
+ modell, så varje varv mäter en faktisk modellskillnad i stället för brus mellan
614
+ en fast produktionsmix och en enda experimentmodell.
615
+ `default` som modellvärde ger agentdefaults (dagens produktionsblandning) och
616
+ kör utan override; katalognamnet blir då `baseline/`, för alla andra värden
617
+ `<modell>/`. Arkivera all output sida vid sida under experimentytan.
618
+ Uppgiften samlar underlag; människan analyserar. Ingen dom fälls över vilken
619
+ modell som är bäst, och ingen dom simuleras: en OLIKA i något varvs
620
+ dubbelkörning väntar på `döm`, precis som i produktion.
621
+
622
+ **Underkommandon resolvas först.** Är det enda argumentet `sammanställ` eller
623
+ `rapport`, är det underkommandot `utvärdera sammanställ` respektive
624
+ `utvärdera rapport` (se avsnitten nedan). Argumentreglerna nedan gäller aldrig
625
+ de orden; de klassificeras varken som modell eller post-id.
626
+
627
+ **Flaggorna plockas bort först.** `--om`, `--hoppa-över` och `--körning
628
+ <katalog>` (flaggan plus dess värde) lyfts ur argumentlistan INNAN skanningen
629
+ nedan börjar, och räknas varken som modell eller post-id. Utan det steget
630
+ stoppar svepets egen rad, som slutar med `--körning <katalog>`, skanningen
631
+ omedelbart, noll modeller hittas, och kommandot faller på "Minst en modell
632
+ krävs" trots att modellen står där.
633
+
634
+ **Argumenttolkning.** Kvarvarande avslutande argument avgörs höger till vänster
635
+ för att hitta GRÄNSEN mot post-id-listan: ett argument räknas som modell bara
636
+ om det är ett känt agentmodellnamn (`fable`, `opus`, `sonnet`, `haiku`, …) eller
637
+ literalen `default`, allt annat läses som post-id, och skanningen stannar vid
638
+ det första argumentet som inte är ett modellnamn. Ett argument som varken är
639
+ ett känt modellnamn eller ett giltigt gid/slug är ett FEL, aldrig en gissning:
640
+ visa felet och stanna i stället för att chansa på vilken sida av gränsen det
641
+ hör hemma. De modellargument skanningen hittar bildar sedan modellistan i
642
+ DEKLARATIONSORDNING, vänster till höger.
643
+
644
+ **Minst en modell krävs.** Hittar skanningen ingen modell alls är det ett FEL,
645
+ aldrig en default. Ett kommando som kostar timmar och pengar ska säga vilka
646
+ modeller det kör; en tyst standarduppsättning gör att en körning kan starta med
647
+ en annan modell än den som avsågs, och den kostnaden syns först efteråt.
648
+
649
+ **Dubbletter vägras.** Står samma modellvärde två gånger i listan, vägra
650
+ körningen med ett felmeddelande som förklarar varför: två lika värden pekar på
651
+ samma katalognamn och mäter ingen skillnad mot varandra. Vill du köra samma
652
+ modell en gång till, gör det i en egen invokation, då blir det en ny körning
653
+ under samma modell enligt regeln i Experimentytan.
654
+
655
+ **En namngiven modell rör bara sin egen katalog.** Övriga modellers kataloger
656
+ läses inte och skrivs inte. Det är det som gör att en jämförelse kan byggas i
657
+ etapper: `utvärdera <gid> opus sonnet` idag och `utvärdera <gid> fable` en
658
+ vecka senare ger tre modeller under samma post, utan att de första två körs om.
659
+
660
+ **Omkörning frågas fram, den antas aldrig.** Namnger invokationen en modell som
661
+ redan har körningar under sig, stanna INNAN något dyrt startas och fråga om de
662
+ ska köras om. Ställ EN fråga som täcker alla kolliderande modeller, inte en per
663
+ modell. Svaret kan ges i förväg med `--om` (kör om) eller `--hoppa-över` (rör
664
+ dem inte); bär invokationen någon av dem ställs ingen fråga.
665
+
666
+ En omkörning skriver aldrig över den gamla körningen, den blir `k<N+1>`.
667
+ Frågan gäller alltså kostnaden, inte risken att förlora data.
668
+
669
+ 1. **Underlag.** Som `speca` steg 1: rått önskemål eller gid — vid gid hämtas
670
+ posten läsande via request-sync i huvudkonversationen, uppgiftens ENDA
671
+ Asana-kontakt. Skapa `experiments/<slug>/` och `experiment.json` med
672
+ ticketreferens, datum och `varianter` i deklarationsordning.
673
+
674
+ Anteckna samtidigt postens `permalink_url` i `experiment.json` när gid-svaret
675
+ bär det. Det kommer ur samma läsande hämtning som redan görs, så steget får
676
+ ingen ny Asana-kontakt. Saknas fältet i svaret skrivs det inte, och rapporten
677
+ visar då gid som ren text: Asanas permalänk bär ett workspace-gid som inte
678
+ finns på experimentytan, så en konstruerad URL vore en gissning.
679
+
680
+ **`experiment.json` skrivs läs-modifiera-skriv, aldrig från noll.** Saknas
681
+ filen skapas den. Finns den redan läses den in FÖRST och uppdateras sedan:
682
+ `varianter` slås ihop som UNION med bevarad deklarationsordning, de
683
+ befintliga namnen först och invokationens nya namn efter, och en befintlig
684
+ körnings `status` skrivs aldrig över av en process som inte äger just den
685
+ körningen. En headless-process med en enmodellslista rör alltså bara sin
686
+ egen modell. Utan regeln raderar `fable`-körningen en vecka senare `opus`
687
+ och `sonnet` ur filen, och eftersom det är den här filen
688
+ återupptagningsregeln läser (se Återupptagbart nedan) slutar ett återupptaget
689
+ svep känna igen färdiga körningar och betalar om för dem. Skrivningen ska
690
+ vara ATOMISK: skriv till en temporärfil i samma katalog och byt namn.
691
+ `experiment.json` är den enda filen på experimentytan som delas mellan
692
+ samtidiga processer utan den låsning `archive.mjs ny-korning` ger
693
+ körningskatalogerna, så samtidighetsdisciplinen måste stå här i stället.
694
+
695
+ 2. **Källmanifest EN gång.** Bygg manifestet som i `speca` steg 2 och
696
+ återanvänd det byteidentiskt i ALLA varv. Anteckna manifest-hash i
697
+ `experiment.json` (`ref-hash.mjs` på manifesttexten). Enda avsedda
698
+ skillnaden mellan varven är modellen.
699
+ 3. **Eskaleringsvalet görs en gång** enligt `speca`:s regler och gäller alla
700
+ varv, även eskaleringsvalet är en del av det som hålls konstant.
701
+ Triggar valet: agenten blir `request-specifier-eskalerad` i stället för
702
+ `request-specifier`, likadant i alla varv. Modell-overriden i steg 4
703
+ gäller ändå den eskalerade agenten precis som de två andra: dess egen
704
+ pinnade modell, den som enligt agentdefinitionen ÄR eskaleringen, skrivs
705
+ över av varvets modell. Eskaleringsmekanismen kollapsar alltså medvetet
706
+ under override, det är avsett, inte en lucka: `utvärdera` mäter en ren
707
+ modellskillnad över alla tre rollerna och testar aldrig
708
+ eskaleringsbeslutet i sig.
709
+ 4. **Ett varv per modell i listan**, sekventiellt, i deklarationsordning.
710
+ Varje varv är EN egen headless-process, och det är det som ger mätningen
711
+ gratis. Ansvaret delas, eftersom en process aldrig kan känna sin egen
712
+ slutkostnad:
713
+
714
+ **Startaren** (huvudkonversationen, eller svepets loop) gör per modell:
715
+ allokerar körningskatalogen med `archive.mjs ny-korning`, startar
716
+ `claude -p --model sonnet --output-format json` med katalogen som explicit
717
+ argument, väntar in processen, och skriver JSON-svaret rått till
718
+ `<modell>/k<N>/korning.json`. Rapporten läser sedan `total_cost_usd`,
719
+ `usage.input_tokens`, `usage.output_tokens`,
720
+ `usage.cache_read_input_tokens`, `usage.cache_creation_input_tokens` och
721
+ `subagent_stats.spawned` ur den filen.
340
722
 
341
- Sex deterministiska script (fyra kontroller + `lib.mjs` med de delade
342
- byggstenarna + `ref-hash.mjs` för stabil hashning) ersätter modellkörningar
343
- där kontrollen är ren form —
723
+ **Startaren tar tiden runt processen** och skriver `<modell>/k<N>/matning.json`
724
+ med `startad`, `avslutad` (båda ISO 8601) och `wall_ms` (differensen).
725
+ Filen skrivs ÄVEN när processen faller: en kraschad körning har en klocktid,
726
+ och den är ofta det intressantaste talet i tabellen. Rapporten läser
727
+ väggtiden därifrån, aldrig ur `korning.json`. Skälet är detsamma som för
728
+ kostnaden: en process kan inte känna sin egen klocktid när arbetet ligger i
729
+ bakgrundssubagenter, och svarets `duration_ms` räknar inte in väntan på dem.
730
+ `korning.json` förblir headless-svaret rått, därav den egna filen.
731
+
732
+ Startaren stämplar samtidigt körningens `state.json` med `proveniens`:
733
+
734
+ ```
735
+ node <skillkatalog>/scripts/archive.mjs proveniens <modell>/k<N> \
736
+ --modell <varvets modellvärde> --konto <vilket Claude-konto som körde> \
737
+ --underlag <sökväg till källmanifestet>
738
+ ```
739
+
740
+ Kommandot räknar själv `skill_sha256` (över `SKILL.md` plus de fyra
741
+ agentdefinitionerna) och `underlag_sha256`, läser `korning.json` och
742
+ antecknar `fakturerade_modeller` (den kanoniserade nyckelmängden ur
743
+ `modelUsage`, suffix som `[1m]` strippade — ADR-0099), och stämplar även varje
744
+ `del-N/`. Utan den stämpeln går det inte att avgöra
745
+ vad som skilde `k1` från `k2`, och en jämförelse över tid blir en
746
+ gissning. Anropet står här som ett
747
+ kommando och inte som en beskrivning med flit: ett steg som ålägger
748
+ startaren en skrivning utan att peka ut vem som utför den är en skrivning
749
+ som inte blir av, och den syns först som tomma celler långt senare.
750
+ Kör därför proveniens-stämpeln EFTER att `korning.json` skrivits, aldrig
751
+ före — annars saknar stämpeln faktureringsdatan.
752
+
753
+ **Modellverifiering görs av startaren mot faktureringen (ADR-0099),
754
+ aldrig mot svansraden ensam.** När processen är klar: jämför
755
+ `fakturerade_modeller` mot varvets begärda modell plus orkestratorns
756
+ (sonnet). En modellnyckel som varken är varvets modell eller
757
+ orkestratorns är en faktisk MODELL-avvikelse → körningen blir
758
+ `"ofärdig"`. En MODELL-svansrad som säger något annat än vad
759
+ `modelUsage` visar är en RAPPORTERINGSAVVIKELSE: den visas i rapporten
760
+ och står kvar i `runs[]` (raden bär agentens uppgift i
761
+ `model.uppgiven`), men den fäller ALDRIG ensam körningen — modeller är
762
+ opålitliga källor om sin egen identitet, och att kasta en korrekt
763
+ körning på en felskriven svansrad betalar om ett riktigt resultat.
764
+ Modellvärdet `default` kör agenternas egna defaults och får lagligt
765
+ producera flera modellnycklar: ingen mängdjämförelse görs där.
766
+
767
+ **Den inre processen** känns igen på invokationen, aldrig på gissning: bär
768
+ den `--körning`, är du den inre processen. Allokera då ingen katalog, starta
769
+ ingen ny process, och kör stegen direkt mot den anvisade katalogen. Utan det
770
+ stoppvillkoret läser den inre processen samma uppgift som startaren, startar
771
+ ett varv till och dubblar notan.
772
+
773
+ Den inre processen kör speca-stegen (dispatch, kontraktskontroll,
774
+ arkivering) och därefter bedöm-stegen (analys, poäng,
775
+ ankarlösning, dubbelkörning enligt bedöm) mot den anvisade katalogen, utan
776
+ godkännandegrind, och rör ingenting utanför den. Är modellen `default`
777
+ används ordinarie promptmallar och agenternas defaults, ingen override. Är
778
+ den ett modellnamn får varje Agent-anrop modell-override till den på ALLA
779
+ tre agenter; agentdefinitionerna röres aldrig, effort tas ur definitionen
780
+ som vanligt. Overriden verifieras av STARTAREN mot `modelUsage` (se
781
+ modellverifieringen ovan) — svansraden arkiveras men avgör aldrig ensam.
782
+ `approval` förblir `null`, `state.json`
783
+ får `"experiment": true`. `REQUEST_ID` och `--expect-id` använder alltid
784
+ ticketens gid/slug, aldrig katalogens namn.
785
+
786
+ Orkestratorn pinnas till sonnet i ALLA körningar. Dess kostnad blir då en
787
+ konstant som går att räkna bort, och `modelUsage` i JSON-svaret skiljer den
788
+ från agenternas kostnad utan extra arbete.
789
+
790
+ Subagentdöd, kontraktsbrott (kvarstående BROTT efter omdispatch) eller
791
+ en faktisk MODELL-avvikelse (obegärd nyckel i `modelUsage`, aldrig en
792
+ ensam svansradsavvikelse), i vilket steg det än inträffar, sätter
793
+ körningens `status` till `"ofärdig"` i `experiment.json`.
794
+
795
+ 5. **DELA_UPP körs autonomt.** Ett DELA_UPP-utfall följer bedöms ordinarie
796
+ delningsregler men utan paus för granskare: delkataloger
797
+ `<modell>/k<N>/del-1/ … del-N/` med `split` satt, förälderns `spec.md` +
798
+ `granskarblock.md` kopierade in, ny analys + poäng per del.
799
+ En del utan tak blir `OVER_SKALAN` och delas aldrig vidare. Övriga
800
+ stopputfall (`TILLBAKA_TILL_SPEC`, `KONCEPT`, `DIAGNOS`, `ESKALERA`,
801
+ `INPUT_SAKNAS`) kräver människa eller kund: arkivera utfallet, avbryt det
802
+ varvet, gör det synligt i rapporten när den körs, och övriga körningar i
803
+ listan fortsätter oberoende.
804
+
805
+ **OLIKA väntar på dom.** Skriver dubbelkörningen OLIKA i ett varv:
806
+ arkivera enligt bedöms regler (spärren står), sätt körningens status till
807
+ `"väntar på dom"` i `experiment.json`, synligt i rapporten när den körs.
808
+ Domen är människans även i experimentet: `döm` körs mot körningskatalogen
809
+ när granskaren vill (adresserad via sin sökväg, se `döm`), och ingen dom
810
+ simuleras av uppgiften. Domen i sig skriver inte om `experiment.json`,
811
+ det gör `utvärdera` självt, nästa gång det körs för samma post (se
812
+ Återupptagbart i avsnittet Batch nedan för hur och när statusen går
813
+ till `"klar"` och rapporten kan visa utfallet). Övriga körningar i listan
814
+ fortsätter oberoende oavsett när, eller om, domen fälls.
815
+
816
+ 6. **Rapport.** BLUF i chatten: klart eller ofärdigt per körning, stopputfall,
817
+ och sökvägen till `experiments/<slug>/`. Nämn att `utvärdera rapport` finns
818
+ när underlaget ska läsas sida vid sida. Ingen skrivning mot Asana.
819
+
820
+ ### Batch: flera poster
821
+
822
+ `utvärdera <post1> <post2> … <modell…>` kör varje ticket och modell i en EGEN
823
+ headless-process, sekventiellt:
824
+
825
+ ```
826
+ claude -p --model sonnet --output-format json "/request utvärdera <gid> <modell> --hoppa-över --körning <katalog>"
827
+ ```
828
+
829
+ Orkestratorn är huvudloopens modell och pinnas till sonnet i headless:
830
+ agenternas modeller styrs av definition plus override och verifieras av
831
+ startaren mot `modelUsage` i `korning.json` (ADR-0099) —
832
+ aldrig av huvudloopens val, och aldrig av svansraden ensam.
833
+
834
+ **Raden bär ALLTID `--om` eller `--hoppa-över`, och väljer `--hoppa-över`.**
835
+ Det är inte en stilregel utan det som gör svepet möjligt: en `claude -p`-process
836
+ kan inte svara på en fråga, så en rad utan flagga skulle hänga eller gissa.
837
+ `--hoppa-över` är dessutom precis återupptagningens semantik, ett avbrutet svep
838
+ som startas om plockar upp där det stannade i stället för att betala om för
839
+ allt färdigt.
840
+
841
+ `--körning <katalog>` pekar ut den körningskatalog som `archive.mjs ny-korning`
842
+ redan allokerat åt den ticketen och modellen. Den inre processen arkiverar
843
+ bara dit, aldrig någon annanstans.
844
+
845
+ Svepets loop är startaren och har startarens hela ansvar per rad: den tar tiden
846
+ runt `claude -p`-anropet och skriver `matning.json`, och den kör
847
+ `archive.mjs proveniens` mot körningskatalogen när processen är klar.
848
+
849
+ - **En ticket = ett färskt kontextfönster.** Allt tillstånd bor i arkivet på
850
+ disk; huvudsessionen samlar bara exitstatus och en radssammanfattning per
851
+ ticket. `/clear` mellan tickets behövs inte — processgränsen är
852
+ motsvarigheten. Headless-körningen förutsätter att uppgiften kan köra utan
853
+ interaktiva permission-frågor, vilket håller: den läser repot och skriver
854
+ enbart under `.haus/requests/` — plus EN läsande Asana-hämtning per ticket
855
+ (steg 1), så MCP-behörigheten för läsning måste täcka headless-körning.
856
+ - **Kör vidare vid fel.** En ticket vars gid inte går att hämta eller vars
857
+ process kraschar noteras i sammanfattningen; resten av listan fortsätter.
858
+ - **Återupptagbart:** innan en körning startas kontrolleras arkivet, styrt av
859
+ körningens `status` i `experiment.json`. `"klar"` och `"stopp"` hoppas
860
+ över med en notis. `"ofärdig"` är avbruten data, inget resultat värt att
861
+ skydda: den utlöser aldrig omkörningsfrågan, utan körs om automatiskt som
862
+ en ny `k<N+1>` under samma modell. Den gamla ofärdiga körningen skrivs
863
+ ändå aldrig över, den står kvar som historik över vad som gick fel.
864
+ `"väntar på dom"` OMVÄRDERAS i stället för att hoppas över, det är den
865
+ enda platsen som är aktören för den statusen: kör `archive.mjs sparr` på
866
+ körningskatalogen. Exit 0 betyder att domen är fälld: sätt körningens
867
+ status till `"klar"` och fortsätt där den stannade (rapporten kan köras
868
+ separat när alla körningar för posten är klara). Exit 1 betyder att
869
+ den fortfarande väntar: notera det och gå vidare till nästa ticket.
870
+ `döm` fäller domen men rör aldrig `experiment.json` (se `döm`), det är
871
+ bara genom att `utvärdera` körs igen för samma post som statusen
872
+ uppdateras. Omkörningsfrågan (se `utvärdera` ovan) gäller alltså bara
873
+ `"klar"`-körningar: det är resultat värda att skydda mot ett fingerfel,
874
+ en ofärdig körning har inget att skydda. Körningar är billiga relativt
875
+ risken att lappa ihop halva resultat. Ett svep över många tickets kan
876
+ därmed fortsätta i en senare session utan onödiga omkörningar.
877
+
878
+ ### Sammanställning
879
+
880
+ `utvärdera sammanställ` läser alla `experiments/*/` och skriver
881
+ `experiments/sammanstallning.md`: tabell per ticket med en rad per modell som
882
+ bär poäng, delnings- och stopputfall, plus avvikelsemarkering där
883
+ modellerna skiljer sig. Modellens rad tas ur dess GÄLLANDE körning, den högsta
884
+ `k<N>` vars status i `experiment.json` är `"klar"`. En modell utan klar körning
885
+ representeras av sin senaste `k<N>` och märks `ofullständig` tillsammans med
886
+ den körningens status. Halvfärdiga kataloger märks likadant, aldrig tyst
887
+ utelämnade. Bara läsning — inga agentdispatcher, inga Asana-anrop.
888
+
889
+ ### Rapport
890
+
891
+ `utvärdera rapport [--ut <fil>]` renderar en HTML-rapport över experimentytan
892
+ med `scripts/report.mjs`. Rapporten täcker ALLTID hela ytan; något postfilter
893
+ finns inte, menyn i rapporten är där läsaren väljer post. Utan `--ut` skrivs
894
+ HTML på stdout, annars till filen. Publicera den som artefakt när den ska
895
+ läsas.
896
+
897
+ **Rapporten skrivs ALDRIG automatiskt.** Ingen körning genererar den när den
898
+ blir klar. Den kostar nästan ingenting att regenerera, så det finns ingen
899
+ anledning att skriva den på spekulation, och en fil som ingen bett om är en fil
900
+ ingen litar på.
901
+
902
+ Rapporten är ren läsning: inga agentdispatcher, inga Asana-anrop, inga
903
+ skrivningar i arkivet utöver utdatafilen.
904
+
905
+ Vad den visar: en meny över poster med ticketens titel, dess katalogslug och
906
+ vilka modeller posten har, och per vald post en kolumn per körning. Varje delpost ur
907
+ ett DELA_UPP-utfall är en egen kolumn efter sin körning (`opus / k1 / del-1`),
908
+ eftersom det är där utfallen bor när en post delat sig. Sektionerna är fältvisa,
909
+ samma fält på samma rad tvärs kolumnerna: utfall, poängens inringning med
910
+ golv, tak, steg och drivers,
911
+ specens fyra fält, samt `impact.md` och `granskarblock.md` hopfällda per
912
+ körning. Äldre körningar med klassningsfiler (före ADR-0098) får dessutom
913
+ sin Klassning-sektion renderad som förut — kalibreringshistoriken kastas inte.
914
+ Sist står kostnaden (USD, tokens, väggtid, dispatcher) och proveniensen.
915
+ En delkolumn saknar egen mätning och säger `ingår i k<N>`, aldrig omätt.
916
+ Matrisen är ojämn med flit: en post kan ha två modeller och en annan tre,
917
+ eftersom en dyrare modell läggs till bara där de billigare inte räckte.
918
+
919
+ Vad den ALDRIG gör: rangordnar modeller, rekommenderar en nivå, räknar aggregat
920
+ tvärs poster med olika modelluppsättning, eller uttalar sig om vilket utfall
921
+ som var korrekt. Bedömningen av vad som är ett acceptabelt utfall är
922
+ människans, gjord på erfarenhet av systemet. Rapporten visar spridningen.
923
+
924
+ ## Mekaniska script: `scripts/`
925
+
926
+ Nio deterministiska script bor i katalogen: de fem kontrollerna nedan,
927
+ `archive.mjs` som äger arkivets kataloger och spärrar, `report.mjs` som
928
+ renderar modellrapporten (se `utvärdera rapport`), `lib.mjs` med de delade
929
+ byggstenarna och `ref-hash.mjs` för stabil hashning. Kontrollerna ersätter
930
+ modellkörningar där kontrollen är ren form,
344
931
  billigare, snabbare och utan omdömesglidning. Varje script bär sha256 för de
345
932
  referensfiler vars regler det speglar och VARNAR när en katalogbump ändrat dem
346
933
  (kontraktsdrift ska upptäckas av en check, inte av en kund):
@@ -349,28 +936,39 @@ referensfiler vars regler det speglar och VARNAR när en katalogbump ändrat dem
349
936
  dispatcher: rubriker, exakt ett specblock, 2–5 punkter, förbjuden vokabulär,
350
937
  granskarblocksrader, state.json-fält. Skiljer på BRISTER (exit 1, stoppar)
351
938
  och VARNING (heuristik — visas, stoppar inte).
939
+ - `scripts/resolve-anchors.mjs <run.json> <spec.md> [impact.md]` — löser
940
+ beläggsankare (`belagg_ankare`/`ankare`) till ordagranna källblock, körs
941
+ FÖRE contract-check. Exit 0 = allt löst (eller inga ankare i filen), 1 =
942
+ minst ett OLÖST/FLERTYDIG ankare (kontraktsbrott, mekanisk omdispatch),
943
+ 2 = användningsfel.
352
944
  - `scripts/contract-check.mjs <run.json> <spec.md> [impact.md] [--expect-id <id>]`
353
- — kontraktskontroll av classify/score-JSON EFTER körning: enums,
945
+ — kontraktskontroll av score-JSON EFTER körning: enums,
354
946
  konfidensintervall, poängupplösning, inringningens närvaro (även per
355
947
  alternativ; under-A1-fallet med floor null och tak A1 är giltigt), belägg/
356
948
  driver-citat som ordagranna substrängar av underlaget, protokollterm-
357
949
  svartlista i kundtextfält, sluten kuvertnyckellista, request_id mot katalogen.
358
- INPUT_SAKNAS kräver saknas-lista i stället för frågerader.
359
- - `scripts/compare-classifications.mjs <a.json> <b.json>` — deterministisk
360
- dubbelkörningsdiff: utfall, basklass, varje frågas svar och konfidensens sida
361
- om 0.8-gränsen. Skriver `LIKA` eller `OLIKA` + diffrader, exit 0/1; exit 2 =
362
- ojämförbara körningar (dispatcherfel, aldrig LIKA).
363
- - `scripts/compare-scores.mjs <a.json> <b.json>` — samma för poäng: outcome,
364
- score, confidence, golv- och takankare. Aldrig prosa. Samma exit-semantik.
365
- Båda compare-scripten kräver mode `pricing`; guard-jämförelser är en medveten
366
- utbyggnad i guard-steget.
950
+ INPUT_SAKNAS kräver saknas-lista i stället för frågerader. Ett olöst ankare
951
+ (belägg saknas trots `belagg_ankare`, eller `ankare` utan `citat`) är
952
+ dispatcherns fel, inte agentens: exit 2 med hänvisning till resolve-anchors.mjs.
953
+ - `scripts/check-lasta.mjs "<LÄSTA-innehåll>" <spec.md> <impact.md>` —
954
+ kontrollerar scorerns läs-scope: LÄSTA-raden ska bära underlagens två
955
+ sökvägar och får därutöver bara bära filer under skillens egen katalog.
956
+ Exit 0 = ok, 1 = kontraktsbrott (BROTT-rader), 2 = användningsfel.
957
+ - `scripts/compare-scores.mjs <a.json> <b.json>` — deterministisk
958
+ dubbelkörningsdiff: outcome,
959
+ score, confidence, golv- och takankare. Aldrig prosa. Skriver `LIKA`,
960
+ `LIKA_TOLERANS` (poängskillnad ≤ 0,5 med samma outcome, oavsett confidence,
961
+ och ankarband som överlappar eller angränsar; exit 0, verdiktraden pekar ut
962
+ körningen med högst poäng) eller `OLIKA` + diffrader; exit 0/1, exit 2 =
963
+ ojämförbara körningar (dispatcherfel, aldrig LIKA). Scriptet kräver mode
964
+ `pricing`; guard-jämförelser är en medveten utbyggnad i guard-steget.
367
965
 
368
966
  Kontrollerna bedömer aldrig innehåll och returnerar aldrig protokollutfall —
369
967
  en post som passerar kan fortfarande stoppas av protokollen själva, och det är
370
968
  deras jobb. Vid script-fel (exit 2 eller krasch): visa felet och stanna, kör
371
969
  aldrig vidare som om kontrollen passerat.
372
970
 
373
- ## Dispatch-kontrakt (gäller alla fyra agenterna)
971
+ ## Dispatch-kontrakt (gäller alla tre agenterna)
374
972
 
375
973
  - Prompten är självbärande: agenten ser inte den här konversationen.
376
974
  - **Promptmallar, etiketterna ordagrant:**
@@ -400,31 +998,32 @@ aldrig vidare som om kontrollen passerat.
400
998
  <granskarblock.md bytetroget>
401
999
  ```
402
1000
 
403
- `request-classifier` / `request-scorer`:
1001
+ `request-scorer` (underlag per sökväg, ADR-0099 — aldrig inklistrat):
404
1002
 
405
1003
  ```
406
1004
  REQUEST_ID: <gid eller slug>
407
1005
  MODE: <pricing|guard>
408
1006
  DELPOST: <nej | ja, del N av M>
409
1007
 
410
- SPEC:
411
- <spec.md bytetroget>
412
-
413
- PÅVERKANSANALYS:
414
- <impact.md bytetroget>
1008
+ SPEC: <absolut sökväg till spec.md>
1009
+ PÅVERKANSANALYS: <absolut sökväg till impact.md>
415
1010
  ```
416
1011
 
417
- (guard-läge lägger till `FULLPLAN:` och `TIDIGARE_KLASSNING:` /
418
- `TIDIGARE_POANG:` — guard-kommandot kommer i ett senare steg.)
1012
+ (guard-läge lägger till `FULLPLAN:` och `TIDIGARE_POANG:` —
1013
+ guard-kommandot kommer i ett senare steg.)
419
1014
 
420
1015
  - **Svansrader efter huvudinnehållet**, i denna ordning: `MODELL: <modell-id>`
421
1016
  (alla agenter), `LÄSTA: <kommaseparerade absoluta sökvägar eller "inga">`
422
- (endast classifier/scorer — deras Read är promptbegränsad, raden gör regeln
1017
+ (endast scorern — dess Read är promptbegränsad, raden gör regeln
423
1018
  granskbar), `AVVIKELSE: <text>` (endast om något i prompten bröt mot agentens
424
1019
  regler, t.ex. en prislista i underlaget). Svansraderna strippas före varje
425
1020
  maskinell kontroll och före arkivering; MODELL antecknas i runs-raden,
426
- LÄSTA verifieras ligga under agentens tillåtna katalog, AVVIKELSE visas
427
- alltid för användaren.
1021
+ AVVIKELSE visas alltid för användaren. LÄSTA kontrolleras MEKANISKT av
1022
+ `scripts/check-lasta.mjs` (ADR-0099): raden ska bära underlagens två
1023
+ utpekade sökvägar (båda obligatoriska) och får därutöver bara bära filer
1024
+ under skillens egen katalog (referensfilen) — allt annat är ett
1025
+ kontraktsbrott och behandlas som BROTT i bedöm steg 5 (omdispatch EN
1026
+ gång, sedan stopp), aldrig bara en notis.
428
1027
  - Agentens svar i övrigt är rådata enligt skillens kontrakt (prosa-dokument
429
1028
  eller JSON), ingen kommentar runt om.
430
1029
  - **Omdispatch (max EN per agent och varv):** skicka HELA ursprungsprompten
@@ -434,6 +1033,11 @@ aldrig vidare som om kontrollen passerat.
434
1033
  <felraderna från kontrollen, citerade ordagrant>
435
1034
  ```
436
1035
  En ny subagent minns inget — enbart felrader utan underlag är obrukbart.
1036
+ **Omtaget bokförs alltid (ADR-0099):** det förkastade försöket arkiveras
1037
+ med `-avvisad`-suffix (prissättning: `<typ>-pricing-vN-avvisad.json`;
1038
+ textsteg: `spec-avvisad-<datum>.md` / `impact-avvisad-<datum>.md`) och får
1039
+ en egen runs-rad med kontrollens felrader — två körningar av samma steg
1040
+ utan spår av varför är precis den lucka kvittot stänger.
437
1041
 
438
1042
  ## Använd inte när
439
1043
 
@@ -442,5 +1046,5 @@ aldrig vidare som om kontrollen passerat.
442
1046
  - Frågan gäller Asana-uppsättning ("koppla repot") — det är request-sync:s
443
1047
  uppsättningsflöde, kör den direkt.
444
1048
  - Guard-läget ("kör guarden", före första kodrad) — körs tills vidare via
445
- request-classify/request-score direkt; ett `guard`-kommando kommer i ett
1049
+ request-score direkt; ett `guard`-kommando kommer i ett
446
1050
  senare steg.