@haus-tech/haus-workflow 5.6.1 → 5.8.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.
@@ -1,123 +1,161 @@
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-score m.fl.) fungerar fortfarande fristående; den här skillen äger kedjan, arkivet och dispatchen till subagenter.
3
+ description: The single entry point to Haus's request flow. Use when the user says "/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>", or when several request skills are to be chained in sequence. Individual request skills (request-write-spec, request-score and others) still work standalone; this skill owns the chain, the archive and the dispatch to subagents.
4
4
  ---
5
5
 
6
- # /request — requestflödets ingång
7
-
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
- respektive skill. Här bor bara routing, kontextbärande, arkivet och dispatchen.
11
-
12
- ## Uppgifter
13
-
14
- `/request [uppgift]` — utan argument: visa menyn nedan.
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-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 |
27
-
28
- ## Hårda regler
29
-
30
- - **request-sync körs ALDRIG i en subagent.** Uppsättning och brädverifiering är
31
- interaktiva, fältkartan är sessionsstate, och en enda skrivare mot Asana
32
- eliminerar races. All Asana-läsning/skrivning sker i huvudkonversationen via
33
- Skill-verktyget (`request-sync`).
34
- - **En skrivare i taget mot Asana.** Batchresultat skrivs tillbaka sekventiellt,
35
- en post i taget, aldrig parallellt.
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,
52
- inte en fysisk spärr. Därför: lägg aldrig prislistans innehåll i prompten,
53
- och lägg inga egna sökvägar i dispatcherns rader utöver etiketterna i mallen.
54
- Sökvägar som redan står i underlaget (analysen namnger filer,
55
- det är dess jobb) är inte ett brott — regeln gäller det dispatchern
56
- lägger till. Granska `LÄSTA:`-raden i svaret (se dispatch-kontraktet).
57
- - **Granskarblocken når aldrig kunden.** Specens granskarblock arkiveras separat
58
- (`granskarblock.md`). Analysens "## Till granskaren"-block ligger kvar i
59
- `impact.md` (det är del av underlaget för poängsättningen) — men inget
60
- granskarblock får någonsin skickas med i material som går till request-sync
61
- för kundsynliga fält.
62
- - **Arkivet är cache, Asana är sanningen.** Vid konflikt vinner brädan.
63
- - **Godkännande antas aldrig.** Att en spec finns i arkivet betyder inte att den
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-workflow/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.
76
-
77
- ## Arkivet: `.haus-workflow/requests/`
78
-
79
- Orkestreringens kuvert. De haus-hanterade request-skillsen känner inte till det;
80
- den här skillen (och agenterna via sina prompter) underhåller det.
81
-
82
- **En katalog = en spec = en prissättningsbar post.** En bunt (flera specar ur
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.
6
+ # /request: the entry point to the request flow
7
+
8
+ Thin orchestration on top of the four request skills. This skill contains
9
+ **zero domain rules**: everything about the spec format, scores and Asana fields lives in
10
+ the respective skill. Only routing, context carrying, the archive and the dispatch live here.
11
+
12
+ ## Tasks
13
+
14
+ `/request [task]`: without an argument, show the menu below.
15
+
16
+ | Task | Does |
17
+ | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
18
+ | `speca <request or gid>` | Source manifest → dispatch `request-specifier` → archive spec + reviewer block |
19
+ | `bedöm <gid, slug or nr>` | Gate → secure an analysis → dispatch `request-scorer` → archive JSON |
20
+ | `status` | Queue state via request-sync, BLUF table with row numbers |
21
+ | `godkänn <nr or gid>` | Move Offer status forward via request-sync; see the gate section |
22
+ | `underkänn <nr or gid> <reason>` | Reason as an Asana comment + iteration counter via request-sync |
23
+ | `döm <item>` | Human ruling on OLIKA: choose a run, order a rerun or reject the source material |
24
+ | `detaljer <gid, slug or nr> [step]` | Print archived JSON/documents for the item |
25
+ | `utvärdera <item…> <model…>` | Run the flow once per named model against the experiment area, at least one model; `sammanställ` gives the sweep view, `rapport` a readable comparison |
26
+ | `hjälp` | Explain the tasks, touch no files |
27
+
28
+ ## Hard rules
29
+
30
+ - **request-sync NEVER runs in a subagent.** Setup and board verification are
31
+ interactive, the field map is session state, and a single writer against Asana
32
+ eliminates races. All Asana reading/writing happens in the main conversation via
33
+ the Skill tool (`request-sync`).
34
+ - **One writer at a time against Asana.** Batch results are written back sequentially,
35
+ one item at a time, never in parallel.
36
+ - **Documents are pasted byte-faithfully** into the specifier and assessor prompts
37
+ (request, spec, reviewer block). A truncated or reformatted
38
+ paste moves the source text out from under the agent's feet and gives a silently wrong
39
+ answer. Never paste a summary.
40
+ - **The pricing's source material is pointed out by path (ADR-0099).** The scorer's
41
+ prompt carries two named absolute paths (`spec.md`, `impact.md`) instead
42
+ of pasted text: the agent reads the files itself via Read, so
43
+ no transcript can be truncated, and the evidence anchors (source + substring)
44
+ are resolved by the scripts against exactly the same files on disk. The read scope is EXACTLY
45
+ these two files plus the skill's own directory, never
46
+ the run directory as a whole: `granskarblock.md` is the assessor's source material
47
+ and older pricing JSON would be pure contamination of the double run's
48
+ independence.
49
+ - **The price list and the repo are kept away from the scorer.** Its
50
+ agent definition lacks search and shell tools (Grep/Glob/Bash), and Read
51
+ is prompt-restricted per the rule above: the read scope is a prompt rule,
52
+ not a physical barrier. Therefore: never put the price list's content in the prompt,
53
+ and add no paths of your own to the dispatcher's lines beyond the labels in the template.
54
+ Paths that already stand in the source material (the analysis names files,
55
+ that is its job) are not a breach: the rule applies to what the dispatcher
56
+ adds. Check the `LÄSTA:` line in the reply (see the dispatch contract).
57
+ - **The reviewer blocks never reach the customer.** The spec's reviewer block is archived separately
58
+ (`granskarblock.md`). The analysis's "## For the reviewer" block stays in
59
+ `impact.md` (it is part of the source material for the scoring), but no
60
+ reviewer block may ever be sent along in material that goes to request-sync
61
+ for customer-visible fields.
62
+ - **The archive is a cache, Asana is the truth.** On conflict, the board wins.
63
+ - **Approval is never assumed.** That a spec exists in the archive does not mean it
64
+ has been reviewed. See the gate under `bedöm`.
65
+ - **Experiment data never reaches Asana.** `utvärdera` never writes via
66
+ request-sync; its only Asana contact is the read-only fetch of the request in the
67
+ main conversation. The `experiments/` tree is not item directories: sync,
68
+ batch and guard read only `.haus-workflow/requests/<gid-eller-slug>/` at the top level
69
+ and never touch `experiments/`.
70
+ - **The machine reruns only on a script-proven contract breach.** Redispatch
71
+ (budget ONE per agent) answers an error that precheck, resolve-anchors or
72
+ contract-check has proven. A disagreement between two VALID runs is a
73
+ judgment question and always goes to the human via the task `döm`: more
74
+ machine runs produce more votes, never more truth. No new
75
+ rerun mechanism may be added that breaks this.
76
+
77
+ ## The language contract
78
+
79
+ One rule: **internal artifacts and instructions are written in English, customer-facing
80
+ text in the customer's language.** Ownership is per document, never per sentence. This
81
+ table is the only place in this skill where the rule is stated in full; every other
82
+ section follows it.
83
+
84
+ | Document | Owner | Language |
85
+ | --------------------------------------------------------------------------------------------------------- | ------------------- | ------------------------------------ |
86
+ | `request/SKILL.md` | internal | English |
87
+ | `request-write-spec/SKILL.md` + `references/specformat.md` | internal | English |
88
+ | `request-assess-impact/SKILL.md` | internal | English |
89
+ | `request-score/SKILL.md` | internal | English |
90
+ | `request-score/references/poangskala.md` | internal | **Swedish, untouched** (`sv, orörd`) |
91
+ | `request-sync/SKILL.md` | internal | English, Asana literals verbatim |
92
+ | `request-specifier(-eskalerad)`, `request-assessor`, `request-scorer` | internal | English |
93
+ | `spec.md` | **customer-facing** | the customer's (ADR-0114, unchanged) |
94
+ | `impact.md` | internal | English |
95
+ | `granskarblock.md` | internal | English |
96
+ | score JSON: `customer_rationale`, `conditions`, `assumptions_stated`, `alternatives`, `coverage_post.yta` | **customer-facing** | the customer's (unchanged) |
97
+ | score JSON: evidence, bracketing, drivers, everything else | internal | English |
98
+ | `korning.json`, `state.json`, `matning.json`, `experiment.json` | machine | unchanged |
99
+ | The Asana description's three sections | **customer-facing** | the customer's (unchanged) |
100
+ | Asana subtask `Analys` and its rows | internal | English body text, **Swedish title** |
101
+
102
+ The subtask titles (`Analys`, `Påverkansanalys vN`, `Poängsättning vN`) stay in
103
+ Swedish. They are lookup keys in Asana, the same class as the field names: `request-sync`
104
+ looks up an existing `Analys` and reuses it, and a rename would split the items into
105
+ before and after without anyone noticing.
106
+
107
+ Where this skill names a heading or row of the analysis or the reviewer block, it uses
108
+ the English literal the agents write: `## For the reviewer`, _Deviation from the spec_
109
+ with the labels `none`, `refinement`, `contradiction`, _Unanswered outcome choices_,
110
+ _Price-decisive open choice_. Archived runs carry the Swedish forms (`## Till granskaren`,
111
+ _Avvikelse mot specen_ with `ingen`, `precisering`, `motsägelse`,
112
+ _Obesvarade utfallsval_, _Prisavgörande öppet val_); the scripts read both, and their
113
+ output lines keep the Swedish canonical key (`AVVIKELSE: motsägelse`).
114
+
115
+ ## The archive: `.haus-workflow/requests/`
116
+
117
+ The orchestration's envelope. The haus-managed request skills do not know about it;
118
+ this skill (and the agents through their prompts) maintains it.
119
+
120
+ **One directory = one spec = one priceable item.** A bundle (several specs from
121
+ the same request) becomes several directories: the score is set per item, so a directory
122
+ with several specs cannot be assessed.
85
123
 
86
124
  ```
87
125
  <repo-rot>/.haus-workflow/requests/<gid-eller-slug>/
88
- ├── spec.md EN spec, bytetrogen — det granskaren godkänner
89
- ├── granskarblock.md "## Till granskaren" (bunt: delens eget
90
- │ block, aldrig ett kopierat, ADR-0118)
91
- ├── impact.md påverkansanalysen, ordagrann inkl. sitt
92
- │ Till granskaren-block
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)
126
+ ├── spec.md ONE spec, byte-faithful: what the reviewer approves
127
+ ├── granskarblock.md "## For the reviewer" (bundle: the part's own
128
+ │ block, never a copied one, ADR-0118)
129
+ ├── impact.md the impact analysis, verbatim incl. its
130
+ │ For the reviewer block
131
+ ├── score-pricing-vN.json new round = new file, never overwrite
132
+ ├── score-guard-vN.json guard outcomes are never synced, only archived
133
+ │ (own N sequence per type, separate from pricing)
96
134
  └── state.json
97
135
  ```
98
136
 
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
-
109
- - Katalognamn: Asana-gid när posten finns på brädan, annars en kebab-slug av
110
- spec-rubriken. Bunt utan gid: `<slug>-1`, `<slug>-2`, … När gid myntas senare:
111
- döp om katalogen till gid och notera sluggen i `state.json.former_slug`.
112
- - **Gitignore ägs av haus, inte av dig.** `.haus-workflow/requests/` står i den
113
- haus-hanterade `.gitignore`-blocken och skrivs av `haus apply` — lägg aldrig till
114
- raden själv, och hand-editera aldrig blocket. Står arkivet ändå spårat i git:
115
- kör `haus apply --write`, som både skriver regeln och avspårar filerna.
116
- Prisunderlag ska inte in i kundens historik. `.haus-workflow/request.json` (syncens
117
- config) förblir spårad — den är konfiguration, inte arbetsmaterial.
118
- - **Gammalt arkiv under `.haus/`:** flyttas av `haus apply` till
119
- `.haus-workflow/` (ADR-0103). Skriv aldrig till `.haus/` igen, och flytta inte
120
- själv — kör `haus apply --write` och låt kommandot göra det.
137
+ Older archives (before ADR-0098) also carry `classification-pricing-vN.json` /
138
+ `classification-guard-vN.json` from the removed class axis. They are read and
139
+ shown as before (`detaljer`, the report, `archive.mjs`) but never written by
140
+ new runs.
141
+
142
+ Rejected attempts carry an `-avvisad` suffix and stand outside the N count:
143
+ the pricing's `<typ>-pricing-vN-avvisad.json` and the text steps'
144
+ `spec-avvisad-<datum>.md` / `impact-avvisad-<datum>.md` (ADR-0099); see the
145
+ redispatch rule in the dispatch contract.
146
+
147
+ - Directory name: the Asana gid when the item is on the board, otherwise a kebab slug of
148
+ the spec heading. A bundle without a gid: `<slug>-1`, `<slug>-2`, … When a gid is minted later:
149
+ rename the directory to the gid and note the slug in `state.json.former_slug`.
150
+ - **The gitignore is owned by haus, not by you.** `.haus-workflow/requests/` stands in the
151
+ haus-managed `.gitignore` block and is written by `haus apply`: never add
152
+ the line yourself, and never hand-edit the block. If the archive is still tracked in git:
153
+ run `haus apply --write`, which both writes the rule and untracks the files.
154
+ Pricing material must not enter the customer's history. `.haus-workflow/request.json` (the sync's
155
+ config) stays tracked: it is configuration, not working material.
156
+ - **Old archive under `.haus/`:** moved by `haus apply` to
157
+ `.haus-workflow/` (ADR-0103). Never write to `.haus/` again, and do not move it
158
+ yourself: run `haus apply --write` and let the command do it.
121
159
 
122
160
  ### `state.json`
123
161
 
@@ -140,51 +178,52 @@ omdispatch-regeln i dispatch-kontraktet.
140
178
  }
141
179
  ```
142
180
 
143
- - `bundle` = specar ur samma önskemål (sätts av speca vid bunt).
144
- `split` = delposter ur ett DELA_UPP-utfall (sätts av bedöm). Båda formen
145
- `{parent…, part: N, of: M}`. Aldrig båda på samma katalog.
146
- - `stopp` = ett stopputfall som scripten bevisat utan scorer:
181
+ - `bundle` = specs from the same request (set by speca on a bundle).
182
+ `split` = sub-items from a DELA_UPP outcome (set by bedöm). Both have the form
183
+ `{parent…, part: N, of: M}`. Never both on the same directory.
184
+ - `stopp` = a stop outcome that the scripts have proven without a scorer:
147
185
  `{ "utfall": "KONCEPT" | "DIAGNOS" | "TILLBAKA_TILL_SPEC" | "ESKALERA", "kalla": "spec.md" | "impact.md" | "granskarblock.md", "date": "YYYY-MM-DD", "orsak"?: string }`.
148
- Skrivs BARA av `archive.mjs stopp` (som själv kontrollerar att källan bär
149
- det påstådda: kandidatmarkören i `spec.md`, etiketten `motsägelse` i
150
- `impact.md`, det prisavgörande öppna valet i `granskarblock.md`, ADR-0118)
151
- och nollas BARA av `archive.mjs respec`. `orsak` sätts när `utfall` är
152
- `ESKALERA` (kommandot kräver `--orsak` då, så en människa ser varför posten
153
- inte gick tillbaka till specen en gång till) och saknas annars.
154
- `archive.mjs set-status priced` vägrar medan fältet är satt. Äldre arkiv
155
- saknar fältet och läses som `null`.
156
- - `respec_varv` är en monoton räknare, höjd med 1 av varje `archive.mjs
157
- respec`-anrop (#505), aldrig nollad. Grinden i bedöm läser den för att
158
- avgöra om ett kvarstående prisavgörande öppet val ska stoppa med
159
- `TILLBAKA_TILL_SPEC` (första varvet) eller `ESKALERA` (andra varvet och
160
- senare): en post går aldrig en tredje specrunda på samma val. Äldre arkiv
161
- saknar fältet och läses som `0`.
162
- - `coverage_post` speglar score-kuvertets valfria `coverage_post: { yta,
163
- omfattning }` (ADR-0119). `archive.mjs add-run` kopierar fältet från den
164
- senaste pricing-filen som bär det, skelettet (`ny-korning`/`ny-del`) sätter
165
- det till `null`. `bluf` visar `täckningspost: <yta>` i `anm`-kolumnen när
166
- fältet är satt.
167
- - `approval` skrivs av grinden i bedöm:
186
+ Written ONLY by `archive.mjs stopp` (which itself checks that the source carries
187
+ what is claimed: the candidate marker in `spec.md`, the label `contradiction` in
188
+ `impact.md`, the price-decisive open choice in `granskarblock.md`, ADR-0118)
189
+ and reset ONLY by `archive.mjs respec`. `orsak` is set when `utfall` is
190
+ `ESKALERA` (the command requires `--orsak` then, so a human sees why the item
191
+ did not go back to the spec once more) and is absent otherwise.
192
+ `archive.mjs set-status priced` refuses while the field is set. Older archives
193
+ lack the field and are read as `null`.
194
+ - `respec_varv` is a monotonic counter, raised by 1 by every `archive.mjs respec`
195
+ call (#505), never reset. The price-decisive stop in `speca` (step 6, ADR-0118)
196
+ and `routaSpecStopp` in `scripts/korning.mjs` read it to decide whether a
197
+ remaining price-decisive open choice is to stop with
198
+ `TILLBAKA_TILL_SPEC` (the first round) or `ESKALERA` (the second round and
199
+ later): an item never goes a third spec round on the same choice. Older archives
200
+ lack the field and are read as `0`.
201
+ - `coverage_post` mirrors the score envelope's optional `coverage_post: { yta, omfattning }`
202
+ (ADR-0119). `archive.mjs add-run` copies the field from the
203
+ latest pricing file that carries it, the skeleton (`ny-korning`/`ny-del`) sets
204
+ it to `null`. `bluf` shows `täckningspost: <yta>` in the `anm` column when
205
+ the field is set.
206
+ - `approval` is written by the gate in bedöm:
168
207
  `{ "source": "asana" | "manual", "by": "<namn, krävs vid manual>",
169
208
  "status_read": "<värde>", "date": "YYYY-MM-DD" }`.
170
- Anteckningen är spårbarhet, aldrig själva godkännandet.
171
- - `granskarutslag` är en lista, en rad per dom, skriven via `archive.mjs utslag`, aldrig för hand.
172
- Radform: `{ "typ": "viktning" | "kalibrering" | "sakandring", "date": "YYYY-MM-DD", "beslutad_av": "<namn>", ... }`.
173
- Viktning bär dessutom `beslut` (ordagrant), `axel` (`score`; `classification`
174
- förekommer i äldre arkiv), `n` och `galler_fil`: raden ÄR spärrlyftet,
175
- `archive.mjs sparr` läser den.
176
- Kalibrering bär `fraga`, `lasning1`, `lasning2`: en notis om att en
177
- protokollfråga lät två giltiga läsningar divergera, underlag för nästa
178
- instruktionsversion. Sakändring bär `beslut`: kvittot i `state.json` för
179
- samma händelse som citatblocket i `impact.md` (se `döm`, vägen "Underlaget
180
- är fel"). Det ordagranna beslutet bor i citatblocket, raden här är bara
181
- revisionsspåret, aldrig underlaget självt.
182
- Listan läses medvetet ALDRIG tillbaka in i någon prompt, oavsett typ: ett
183
- utslag som når agenterna styr nästa bedömning i stället för att dokumentera
184
- den föregående. Nollas (töms) vid respec, av samma skäl som `approval`
185
- nollas (se `speca` steg 5): ett utslag mot en ersatt spec är inte längre
186
- giltigt. Bor ENDAST i `state.json`. Rader skrivs aldrig om.
187
- - `runs[]` får en rad per dispatch, även spec- och analyskörningar:
209
+ The note is traceability, never the approval itself.
210
+ - `granskarutslag` is a list, one row per ruling, written via `archive.mjs utslag`, never by hand.
211
+ Row form: `{ "typ": "viktning" | "kalibrering" | "sakandring", "date": "YYYY-MM-DD", "beslutad_av": "<namn>", ... }`.
212
+ A weighting (`viktning`) additionally carries `beslut` (verbatim), `axel` (`score`; `classification`
213
+ occurs in older archives), `n` and `galler_fil`: the row IS the lifting of the hold,
214
+ `archive.mjs sparr` reads it.
215
+ A calibration (`kalibrering`) carries `fraga`, `lasning1`, `lasning2`: a notice that a
216
+ protocol question let two valid readings diverge, material for the next
217
+ instruction version. A factual change (`sakandring`) carries `beslut`: the receipt in `state.json` for
218
+ the same event as the quote block in `impact.md` (see `döm`, the path "The source material
219
+ is wrong"). The verbatim decision lives in the quote block, the row here is only
220
+ the audit trail, never the source material itself.
221
+ The list is deliberately NEVER read back into any prompt, regardless of type: a
222
+ ruling that reaches the agents steers the next assessment instead of documenting
223
+ the previous one. It is reset (emptied) on respec, for the same reason that `approval`
224
+ is reset (see `speca` step 5): a ruling against a replaced spec is no longer
225
+ valid. It lives ONLY in `state.json`. Rows are never rewritten.
226
+ - `runs[]` gets one row per dispatch, including spec and analysis runs:
188
227
  `{ "file": "spec.md" | "impact.md" | "<run>.json", "skill": "...",
189
228
  "agent": "...",
190
229
  "model": { "uppgiven": "<ur MODELL-svansraden>", "fakturerad": "<känd fakturerad modell, annars null>" },
@@ -194,670 +233,670 @@ omfattning }` (ADR-0119). `archive.mjs add-run` kopierar fältet från den
194
233
  "underlag_sha256": { "spec.md": "<sha256>", "impact.md": "<sha256>" },
195
234
  "double_run": "bekräftad" | "olika" | "tolerans" | null,
196
235
  "superseded_by": null, "partial_rerun": false, "pairs_with": null }`.
197
- `model` bär två fält (ADR-0099): `uppgiven` är agentens självrapport ur
198
- MODELL-svansraden, `fakturerad` fylls när dispatchern känner den
199
- fakturerade modellen (`add-run --fakturerad`), annars `null` — faktureringens
200
- sanning per körning bor i `proveniens.fakturerade_modeller`. Äldre arkiv bär
201
- `model` som ren sträng och läses som förut.
202
- `quote_repairs` förekommer i äldre arkiv (före ADR-0092) och läses men
203
- skrivs aldrig av nya körningar.
204
- Stämplingen görs alltid via `archive.mjs add-run`; hur N räknas ut, vilket
205
- suffix en fil får och hur en riktad omkörnings parning skrivs bor i
206
- scriptet och testerna. VILKA `--typ` som skickas till `archive.mjs next-n`,
207
- alltså vilka axlar som faktiskt kördes i varvet, är fortfarande
208
- orkestratorns beslut, inte scriptets.
209
- `underlag_sha256` är underlagsproveniensen: hashen för de dokument körningen
210
- faktiskt såg. Precheck vägrar bedöma när analysens spec-hash inte matchar
211
- nuvarande `spec.md`, och guard ska vägra diffa mot en körning vars underlag
212
- bytts ut — utan detta är ett respec-varv mot gammal analys osynligt.
213
- Referens-hashen bor i run-raden (inte på toppnivå) och räknas vid körning med
214
- `node <skillkatalog>/scripts/ref-hash.mjs <referensfil>` — ALDRIG rå
215
- `shasum` på hela filen: installern stämplar filerna med paketversionen, så en
216
- hel-fils-hash byter värde vid varje haus-release och skulle ogiltigförklara
217
- alla arkiverade körningar. Samma verktyg används för `underlag_sha256`.
218
- Referensfilerna ligger under Claude-konfigkatalogen (`CLAUDE_CONFIG_DIR`
219
- eller `~/.claude`). En senare guard-körning jämför hash och vägrar diffa vid
220
- skevhet ("ompoängsättning krävs, skalan har ändrats").
221
- - `status` blir ALDRIG `priced` när något stopputfall finns i varvets
222
- körningar, eller när den GÄLLANDE raden har `double_run: "olika"` olyft, eller
223
- när den gällande raden bär `partial_rerun: true` utan en matchande
224
- dom (en beställd omkörning väntar på granskaren, se uppgiften `döm`).
225
- `archive.mjs sparr` avgör bägge skälen identiskt (`sparrSkal`).
226
- Spärren lyfts av ett viktningsutslag (`archive.mjs sparr` är den enda tolken,
227
- se uppgiften `döm`) eller av en beställd omkörning vars dom landat i ett
228
- viktningsutslag; ingen annan väg lyfter den, och den oeniga axelns äldre
229
- rader skrivs aldrig om även efter lyft.
230
- `double_run: "tolerans"` räknas som bekräftad och blockerar inte `priced`.
231
- I äldre arkiv som bär en klassaxel (före ADR-0098) vägrar
232
- `archive.mjs set-status priced` därutöver ett OFULLSTÄNDIGT par:
233
- klass- och poängaxelns gällande varvnummer skiljer sig utan att skillnaden
234
- är en förklarad enaxlig omkörning (`partial_rerun: true` på den högre
235
- axelns gällande rad), eller poängaxeln saknas helt. Ett arkiv utan
236
- klassaxel prövas aldrig mot par-regeln — poängen är den enda axeln.
237
- Det här skälet prövas av `pricedBlockers` (`archive.mjs set-status`),
238
- inte av `archive.mjs sparr` — reglerna bor i scriptet och dess tester.
239
- - `spec_synced` sätts av speca steg 7 (posten lagd på brädan);
240
- `guard_run` reserveras för guard-kommandot (senare byggsteg).
241
-
242
- ### Experimentytan: `experiments/`
243
-
244
- Modellkörningar från `utvärdera` bor i ett eget träd och är aldrig prisposter:
236
+ `model` carries two fields (ADR-0099): `uppgiven` is the agent's self-report from
237
+ the MODELL tail line, `fakturerad` is filled when the dispatcher knows the
238
+ billed model (`add-run --fakturerad`), otherwise `null`: the billing's
239
+ truth per run lives in `proveniens.fakturerade_modeller`. Older archives carry
240
+ `model` as a plain string and are read as before.
241
+ `quote_repairs` occurs in older archives (before ADR-0092) and is read but
242
+ never written by new runs.
243
+ The stamping is always done via `archive.mjs add-run`; how N is computed, which
244
+ suffix a file gets and how a targeted rerun's pairing is written live in
245
+ the script and the tests. WHICH `--typ` values are passed to `archive.mjs next-n`,
246
+ that is, which axes were actually run in the round, is still
247
+ the orchestrator's decision, not the script's.
248
+ `underlag_sha256` is the source-material provenance: the hash of the documents the run
249
+ actually saw. Precheck refuses to assess when the analysis's spec hash does not match
250
+ the current `spec.md`, and guard must refuse to diff against a run whose source material
251
+ has been replaced: without this, a respec round against an old analysis is invisible.
252
+ The reference hash lives in the run row (not at the top level) and is computed at run time with
253
+ `node <skillkatalog>/scripts/ref-hash.mjs <referensfil>`, NEVER raw
254
+ `shasum` on the whole file: the installer stamps the files with the package version, so a
255
+ whole-file hash changes value at every haus release and would invalidate
256
+ all archived runs. The same tool is used for `underlag_sha256`.
257
+ The reference files lie under the Claude config directory (`CLAUDE_CONFIG_DIR`
258
+ or `~/.claude`). A later guard run compares the hash and refuses to diff on
259
+ skew ("rescoring required, the scale has changed").
260
+ - `status` NEVER becomes `priced` when any stop outcome exists in the round's
261
+ runs, or when the CURRENT row has `double_run: "olika"` unlifted, or
262
+ when the current row carries `partial_rerun: true` without a matching
263
+ ruling (an ordered rerun waits for the reviewer, see the task `döm`).
264
+ `archive.mjs sparr` decides both reasons identically (`sparrSkal`).
265
+ The hold is lifted by a weighting ruling (`archive.mjs sparr` is the only interpreter,
266
+ see the task `döm`) or by an ordered rerun whose ruling landed in a
267
+ weighting ruling; no other path lifts it, and the disagreeing axis's older
268
+ rows are never rewritten even after the lift.
269
+ `double_run: "tolerans"` counts as confirmed and does not block `priced`.
270
+ In older archives that carry a class axis (before ADR-0098),
271
+ `archive.mjs set-status priced` additionally refuses an INCOMPLETE pair:
272
+ the class and score axes' current round numbers differ without the difference
273
+ being an explained single-axis rerun (`partial_rerun: true` on the higher
274
+ axis's current row), or the score axis is missing entirely. An archive without a
275
+ class axis is never tested against the pair rule: the score is the only axis.
276
+ This reason is tested by `pricedBlockers` (`archive.mjs set-status`),
277
+ not by `archive.mjs sparr`: the rules live in the script and its tests.
278
+ - `spec_synced` is set by speca step 7 (the item placed on the board);
279
+ `guard_run` is reserved for the guard command (a later build step).
280
+
281
+ ### The experiment area: `experiments/`
282
+
283
+ Model runs from `utvärdera` live in a tree of their own and are never pricing items:
245
284
 
246
285
  ```
247
286
  <repo-rot>/.haus-workflow/requests/experiments/<slug>/
248
- ├── experiment.json ticketreferens, datum, modellista, manifest-hash,
249
- │ status per körning
250
- ├── <modell>/ `default` som modellvärde ger katalognamnet
287
+ ├── experiment.json ticket reference, date, model list, manifest hash,
288
+ │ status per run
289
+ ├── <modell>/ `default` as the model value gives the directory name
251
290
  │ `baseline/`
252
291
  │ ├── k1/
253
- │ ├── k1/steg/ råsvaret per agentsteg (claude -p-JSON), inklusive
254
- │ │ omdispatcher och kundtextfixar
255
- │ ├── k1/korning.json summan över stegen: USD, tokens, modelUsage, antal
256
- │ │ steg (report.mjs läser samma nycklar som förut)
257
- │ ├── k1/matning.json startarens egen mätning: startad, avslutad, wall_ms
258
- │ ├── k2/ senare körning av SAMMA modell
259
- │ └── k2/del-1/ … delkataloger: bunt ur speca (`bundle`) eller
260
- │ delning ur DELA_UPP (`split`), aldrig båda
292
+ │ ├── k1/steg/ the raw reply per agent step (claude -p JSON), including
293
+ │ │ redispatches and customer-text fixes
294
+ │ ├── k1/korning.json the sum over the steps: USD, tokens, modelUsage, number
295
+ │ │ of steps (report.mjs reads the same keys as before)
296
+ │ ├── k1/matning.json the starter's own measurement: startad, avslutad, wall_ms
297
+ │ ├── k2/ a later run of the SAME model
298
+ │ └── k2/del-1/ … part directories: a bundle from speca (`bundle`) or
299
+ │ a split from DELA_UPP (`split`), never both
261
300
  └── <annan modell>/k1/ …
262
301
  ```
263
302
 
264
- - **Två filer, två ägare.** `korning.json` skrivs av `korning.mjs` som summan
265
- över stegens råsvar, som ligger orörda i `steg/`; `matning.json` är
266
- startarens klocka. En haus-nyckel i ett råsvar under `steg/` skulle göra det
267
- till ett blandat format; summan bär därför sina egna nycklar
268
- (`korningsmotor`, `steg`, `modellverifiering`) och råsvaren rörs aldrig
303
+ - **Two files, two owners.** `korning.json` is written by `korning.mjs` as the sum
304
+ over the steps' raw replies, which lie untouched in `steg/`; `matning.json` is
305
+ the starter's clock. A haus key in a raw reply under `steg/` would make it
306
+ a mixed format; the sum therefore carries its own keys
307
+ (`korningsmotor`, `steg`, `modellverifiering`) and the raw replies are never touched
269
308
  (ADR-0113 amends ADR-0094).
270
- - **Ingen körning skrivs någonsin över.** Värdet i en gammal körning är att
271
- kunna jämföra samma modell före och efter en ändring i skillen eller
272
- agentprompterna; skriver en ny körning över den är den jämförelsen borta för
273
- alltid. Varje körning får därför en egen katalog `k<N>` under sin modell.
274
- - **Körningskatalogen ÄR postkatalogen.** Den följer arkivkontraktet
275
- bytetroget (spec.md, granskarblock.md, impact.md, pricing-JSON, state.json,
276
- `runs[]`-reglerna), så `precheck.mjs` och `contract-check.mjs` fungerar
277
- oförändrade, de pekas bara på `<modell>/k<N>/`. Proveniensen följer med gratis.
278
- - **Buntregeln: `del-N/` är antingen bunt eller delning, aldrig båda.** En
279
- bunt ur speca (flera specar ur samma önskemål) läggs som `del-1/ … del-N/`
280
- under körningskatalogen med `bundle` satt i varje dels `state.json`; en
281
- delning ur DELA_UPP läggs likadant men med `split` satt. Del 1 ligger ALDRIG
282
- i körningsroten: roten bär bara `state.json` (skelettet `ny-korning` skrev),
283
- `korning.json` och `matning.json`, så en rot utan `spec.md` men med delar
284
- läses som `bunt (N delar)` av `bluf` och rapporten. DELA_UPP inuti en
285
- buntdel ger `del-N/del-M`. `sparr`, `precheck` och `stopp` körs per del;
286
- `proveniens` och `bluf` pekas på `k<N>` och går rekursivt genom delarna.
287
- **Produktionslayouten är oförändrad** (`speca` steg 5: del 1 behåller
288
- gid-katalogen, syskonen blir `<slug>-N`); det är `korning.mjs` som väljer
289
- experimentlayouten, alltid mot katalogen `--katalog` pekar ut.
290
- - **Numret allokeras aldrig av skillen själv**, alltid med
309
+ - **No run is ever overwritten.** The value of an old run is being
310
+ able to compare the same model before and after a change in the skill or the
311
+ agent prompts; if a new run overwrites it, that comparison is gone
312
+ forever. Every run therefore gets a directory of its own `k<N>` under its model.
313
+ - **The run directory IS the item directory.** It follows the archive contract
314
+ byte-faithfully (spec.md, granskarblock.md, impact.md, pricing JSON, state.json,
315
+ the `runs[]` rules), so `precheck.mjs` and `contract-check.mjs` work
316
+ unchanged, they are only pointed at `<modell>/k<N>/`. The provenance comes along for free.
317
+ - **The bundle rule: `del-N/` is either a bundle or a split, never both.** A
318
+ bundle from speca (several specs from the same request) is placed as `del-1/ … del-N/`
319
+ under the run directory with `bundle` set in each part's `state.json`; a
320
+ split from DELA_UPP is placed the same way but with `split` set. Part 1 NEVER lies
321
+ in the run root: the root carries only `state.json` (the skeleton `ny-korning` wrote),
322
+ `korning.json` and `matning.json`, so a root without `spec.md` but with parts
323
+ is read as `bunt (N delar)` by `bluf` and the report. DELA_UPP inside a
324
+ bundle part gives `del-N/del-M`. `sparr`, `precheck` and `stopp` run per part;
325
+ `proveniens` and `bluf` are pointed at `k<N>` and go recursively through the parts.
326
+ **The production layout is unchanged** (`speca` step 5: part 1 keeps the
327
+ gid directory, the siblings become `<slug>-N`); it is `korning.mjs` that chooses
328
+ the experiment layout, always against the directory `--katalog` points out.
329
+ - **The number is never allocated by the skill itself**, always with
291
330
  `archive.mjs ny-korning <modellkatalog> [--request-id <gid eller slug>]`,
292
- som skapar katalogen atomiskt, skriver skelettet till dess `state.json`
293
- (`"experiment": true`, `"stopp": null`, `approval: null`, tomma `runs[]`)
294
- och skriver katalogens sökväg på stdout. Två samtidiga processer, till
295
- exempel två Claude-konton mot samma checkout, får då `k2` och `k3`,
296
- aldrig samma katalog. Räknar skillen själv förlorar den ena processens
297
- körning tyst.
298
- - `state.json` i en körningskatalog bär markören `"experiment": true` ur
299
- skelettet. `approval` förblir `null` och `status` blir ALDRIG `priced`: ett
300
- experimentresultat kan inte förväxlas med en prispost.
301
- - Katalognamnet `experiments` är reserverat: en spec vars rubrik skulle sluggas
302
- till `experiments` får suffixet `-post`.
303
- - `experiment.json` bär `varianter: []` i deklarationsordning och `status` per
304
- körning: `"klar"`, `"ofärdig"`, `"stopp"`, `"väntar på dom"`. Literalerna
305
- skrivs exakt så, mellanslag och diakritiska tecken inkluderade. Startraden
306
- (`"ofärdig"`, "startad, inte avslutad") bär dessutom startarens `pid`
307
- medan körningen pågår; slutstatusen ersätter raden och tar bort den
308
- (#516). Äldre filer
309
- med `baselinemodell` och `kandidatmodell` läses utan fel; nya skrivningar
310
- använder bara `varianter`. Filen skrivs BARA av `archive.mjs experiment`
311
- (se `utvärdera` steg 1), aldrig för hand.
312
- - `experiments/` ärver gitignore-skyddet från `.haus-workflow/requests/`.
313
-
314
- ## Uppgift: `speca`
315
-
316
- 1. **Underlag.** Rått önskemål inklistrat, eller ett gid — hämta då posten via
317
- request-sync (huvudkonversationen) och använd `## Ursprunglig förfrågan`
318
- ordagrant.
319
- 2. **Källmanifest.** Isolerade subagenter kan inte "leta innan de antar" utan
320
- hjälp. Bygg ett manifest innan dispatch: relevanta grep-träffar i repot
321
- (vyer/termer önskemålet nämner), tidigare specar för samma kund (arkivet +
322
- brädan), ev. brief. **Alla sökvägar i manifestet absoluta.** Finns inget repo
323
- i sessionen: skriv `KÄLLOR: inga — allt blir antaganden` så specifiern vet.
324
- Starten avgör samtidigt två rader som specifiern aldrig får gissa:
325
- `SPRÅK:` (`svenska` eller `engelska`, avläst ur önskemålets brödtext;
326
- rubrikskelettet i `RUBRIKER:` följer det språket, se promptmallen) och
327
- `DEPS:` (`installerade` när `node_modules/` eller motsvarande finns under
328
- REPO-ROT, annars `saknas`; utan repo alltid `saknas`).
329
- Skriv manifestet till `<katalog>/kallmanifest.md` i varje katalog specen
330
- arkiveras i (steg 5), så att `bedöm` kan ge assessorn samma manifest utan
331
- att bygga ett nytt. Manifestets första rad är `SPRÅK: <svenska|engelska>`,
332
- samma värde som prompten fick: `bedöm` läser språket därifrån till
333
- assessorns och scorerns `SPRÅK:`-rad och till `kontroll.mjs --sprak`,
334
- aldrig ur en ny gissning (#521).
335
- Manifestet har sist sektionen `## Avgjorda val`: varje val som en människa
336
- avgjort efter ett tidigare stopp, en rad var, ordagrant som svaret gavs;
337
- `inga` om inga. Specifiern får aldrig flagga ett val som står där.
338
- 3. **Eskaleringsbedömning.** Misstänkt bunt (flera beslut), behörighet/betalning/
339
- oåterkallelig data/personuppgifter, eller offertunderlag → utfallet är
340
- prisbärande och eskaleringen är ett MODELLBYTE, inte en flagga: dispatcha
341
- `request-specifier-eskalerad` (frontier-modell) i stället för
342
- `request-specifier`, med `ESKALERAD: ja` plus orsaken i prompten.
343
- 4. **Dispatcha `request-specifier`** (eller den eskalerade varianten enligt
344
- steg 3) enligt promptmallen i dispatch-kontraktet.
345
- 5. **Arkivera.** Specifierns svar delas per spec (ADR-0118): varje titelrad
346
- startar en ny spec, och specens EGET `## Till granskaren`-block avslutar
347
- den. Ett svar med bara ETT block efter flera specar (gamla formatet) är en
348
- brist: `korSpecifier` dispatchar om HELA specifierprompten en gång med
349
- `RÄTTNINGAR:` innan någon katalog skapas, och kvarstår bristen (delat block
350
- eller ett saknat block) blir steget `ofärdig` direkt, utan katalog. En
351
- spec → en katalog: VARJE spec får sin egen katalog med egen `spec.md`,
352
- eget `granskarblock.md` (delens eget block, aldrig ett kopierat) och eget
353
- `state.json` (bunt: `bundle`-fältet ifyllt). Bunt ur en gid-post:
354
- del 1 behåller gid-katalogen, resterande delar får slug-kataloger med
355
- `bundle.parent_slug` satt till gid:t; syskontasks på brädan mintas först
356
- vid sync. Stämpla varje berörd `state.json` med
331
+ which creates the directory atomically, writes the skeleton to its `state.json`
332
+ (`"experiment": true`, `"stopp": null`, `approval: null`, empty `runs[]`)
333
+ and writes the directory's path to stdout. Two concurrent processes, for
334
+ example two Claude accounts against the same checkout, then get `k2` and `k3`,
335
+ never the same directory. If the skill counts by itself, one process's
336
+ run is silently lost.
337
+ - `state.json` in a run directory carries the marker `"experiment": true` from
338
+ the skeleton. `approval` stays `null` and `status` NEVER becomes `priced`: an
339
+ experiment result cannot be confused with a pricing item.
340
+ - The directory name `experiments` is reserved: a spec whose heading would be slugged
341
+ to `experiments` gets the suffix `-post`.
342
+ - `experiment.json` carries `varianter: []` in declaration order and `status` per
343
+ run: `"klar"`, `"ofärdig"`, `"stopp"`, `"väntar på dom"`. The literals
344
+ are written exactly like that, spaces and diacritics included. The start row
345
+ (`"ofärdig"` with `orsak` `"startad, inte avslutad"`, i.e. started, not
346
+ finished) additionally carries the starter's `pid` while the run is in
347
+ progress; the final status replaces the row and removes it (#516). Older files
348
+ with `baselinemodell` and `kandidatmodell` are read without error; new writes
349
+ use only `varianter`. The file is written ONLY by `archive.mjs experiment`
350
+ (see `utvärdera` step 1), never by hand.
351
+ - `experiments/` inherits the gitignore protection from `.haus-workflow/requests/`.
352
+
353
+ ## Task: `speca`
354
+
355
+ 1. **Source material.** A raw request pasted in, or a gid: then fetch the item via
356
+ request-sync (the main conversation) and use `## Ursprunglig förfrågan`
357
+ verbatim.
358
+ 2. **Source manifest.** Isolated subagents cannot "look before they assume" without
359
+ help. Build a manifest before dispatch: relevant grep hits in the repo
360
+ (views/terms the request mentions), earlier specs for the same customer (the archive +
361
+ the board), any brief. **All paths in the manifest absolute.** If there is no repo
362
+ in the session: write `KÄLLOR: inga — allt blir antaganden` so the specifier knows.
363
+ The start also decides two lines that the specifier must never guess:
364
+ `SPRÅK:` (`svenska` or `engelska`, read from the request's body text;
365
+ the heading skeleton in `RUBRIKER:` follows that language, see the prompt template) and
366
+ `DEPS:` (`installerade` when `node_modules/` or equivalent exists under
367
+ REPO-ROT, otherwise `saknas`; without a repo always `saknas`).
368
+ Write the manifest to `<katalog>/kallmanifest.md` in every directory the spec
369
+ is archived in (step 5), so that `bedöm` can give the assessor the same manifest without
370
+ building a new one. The manifest's first line is `SPRÅK: <svenska|engelska>`,
371
+ the same value the prompt got: `bedöm` reads the language from there into
372
+ the assessor's and the scorer's `SPRÅK:` line and into `kontroll.mjs --sprak`,
373
+ never from a new guess (#521).
374
+ The manifest ends with the section `## Avgjorda val`: every choice that a human
375
+ has decided after an earlier stop, one line each, verbatim as the answer was given;
376
+ `inga` if there are none. The specifier must never flag a choice that stands there.
377
+ 3. **Escalation assessment.** A suspected bundle (several decisions), permissions/payment/
378
+ irreversible data/personal data, or offer material → the outcome is
379
+ price-bearing and the escalation is a MODEL SWITCH, not a flag: dispatch
380
+ `request-specifier-eskalerad` (frontier model) instead of
381
+ `request-specifier`, with `ESKALERAD: ja` plus the reason in the prompt.
382
+ 4. **Dispatch `request-specifier`** (or the escalated variant per
383
+ step 3) per the prompt template in the dispatch contract.
384
+ 5. **Archive.** The specifier's reply is split per spec (ADR-0118): every title line
385
+ starts a new spec, and the spec's OWN `## For the reviewer` block ends
386
+ it. A reply with only ONE block after several specs (the old format) is a
387
+ defect: `korSpecifier` redispatches the WHOLE specifier prompt once with
388
+ `RÄTTNINGAR:` before any directory is created, and if the defect remains (a shared block
389
+ or a missing block) the step becomes `ofärdig` directly, without a directory. One
390
+ spec → one directory: EVERY spec gets its own directory with its own `spec.md`,
391
+ its own `granskarblock.md` (the part's own block, never a copied one) and its own
392
+ `state.json` (bundle: the `bundle` field filled in). A bundle from a gid item:
393
+ part 1 keeps the gid directory, the remaining parts get slug directories with
394
+ `bundle.parent_slug` set to the gid; sibling tasks on the board are minted only
395
+ at sync. Stamp every affected `state.json` with
357
396
  `node <skillkatalog>/scripts/archive.mjs add-run <katalog> --file spec.md
358
397
  --skill request-write-spec --agent <request-specifier|request-specifier-eskalerad>
359
398
  --model <ur MODELL-svansraden> --ref references/specformat.md=<hash via ref-hash.mjs
360
- på request-write-spec/references/specformat.md>`. Scriptet sätter
361
- `underlag_sha256` tomt, en spec har inget underlag i repot — men
362
- referens-hashen måste sättas explicit precis som för poängen, annars
363
- tappas specformatets proveniens (se runs-kontraktet).
364
- **Respec över befintlig katalog invaliderar analysen:** när `spec.md`
365
- ersätts i en katalog som redan har en, kör
366
- `node <skillkatalog>/scripts/archive.mjs respec <katalog>` INNAN den nya
367
- specen skrivs. Kommandot äger hela invalideringen (analysen läggs undan
368
- som superseded, status, `approval`, `granskarutslag` och `stopp` återställs,
369
- `runs[]` rörs aldrig); skriv aldrig de fälten för hand. Skälet står i
370
- runs-kontraktet: en ny spec är ogranskad per definition, ett utslag mot en
371
- ersatt spec är inte längre giltigt, och en gammal analys mot ny spec är
372
- exakt den skevhet proveniens-kontrollen finns för att stoppa.
373
- 6. **Formkontroll.** Kör `node <skillkatalog>/scripts/precheck.mjs <katalog>`
374
- per katalog. BRISTER i spec/granskarblock → dispatcha om specifiern EN gång
375
- enligt omdispatch-regeln. **Det förkastade utkastet får kvitto
376
- (ADR-0099, samma regel som prissättningens `-avvisad`):** arkivera det
377
- som `spec-avvisad-<datum>.md` innan omdispatchen, och stämpla det med en
378
- egen runs-rad via `archive.mjs add-run --file spec-avvisad-<datum>.md
379
- --note "<kontrollens felrader>"` (övriga flaggor som spec-radens). VARJE
380
- dispatch får sin egen runs-rad — även den förkastade — så arkivet bär
381
- både att ett omtag skedde och varför; en spec godkänd i andra försöket
382
- får aldrig se ut som en som blev rätt direkt.
383
- **Omdispatch gäller endast den spec som föll.** I en bunt rörs de specar
384
- som passerade inte: omdispatchprompten bär hela ursprungsprompten plus
385
- `RÄTTNINGAR:` med felraderna för just den specen och raden
386
- `Returnera endast spec <N>`; svaret ersätter bara den katalogens utkast.
387
- **Kvarstår brist efter omdispatch blir utkastet aldrig `spec.md`:** även
388
- det andra utkastet arkiveras som `spec-avvisad-<datum>.md` med egen
389
- runs-rad, katalogen står utan `spec.md` och behåller status `spec_draft`,
390
- och i `utvärdera` blir körningen `"ofärdig"`. Visa bristerna och stanna.
391
- **Kandidat stoppas här, aldrig i bedöm.** Skriver precheck raden
392
- `KANDIDAT: KONCEPT` eller `KANDIDAT: DIAGNOS` (specen bär markörraden ur
393
- specformatets kandidatform), kör direkt
399
+ på request-write-spec/references/specformat.md>`. The script sets
400
+ `underlag_sha256` empty, a spec has no source material in the repo, but
401
+ the reference hash must be set explicitly just as for the score, otherwise
402
+ the spec format's provenance is lost (see the runs contract).
403
+ **A respec over an existing directory invalidates the analysis:** when `spec.md`
404
+ is replaced in a directory that already has one, run
405
+ `node <skillkatalog>/scripts/archive.mjs respec <katalog>` BEFORE the new
406
+ spec is written. The command owns the whole invalidation (the analysis is put aside
407
+ as superseded, status, `approval`, `granskarutslag` and `stopp` are reset,
408
+ `runs[]` is never touched); never write those fields by hand. The reason stands in
409
+ the runs contract: a new spec is unreviewed by definition, a ruling against a
410
+ replaced spec is no longer valid, and an old analysis against a new spec is
411
+ exactly the skew the provenance check exists to stop.
412
+ 6. **Form check.** Run `node <skillkatalog>/scripts/precheck.mjs <katalog>`
413
+ per directory. BRISTER in the spec/reviewer block → redispatch the specifier ONCE
414
+ per the redispatch rule. **The rejected draft gets a receipt
415
+ (ADR-0099, the same rule as the pricing's `-avvisad`):** archive it
416
+ as `spec-avvisad-<datum>.md` before the redispatch, and stamp it with a
417
+ runs row of its own via `archive.mjs add-run --file spec-avvisad-<datum>.md
418
+ --note "<kontrollens felrader>"` (other flags as for the spec row). EVERY
419
+ dispatch gets its own runs row, even the rejected one, so the archive carries
420
+ both that a retake happened and why; a spec approved on the second attempt
421
+ must never look like one that was right directly.
422
+ **Redispatch applies only to the spec that failed.** In a bundle, the specs
423
+ that passed are not touched: the redispatch prompt carries the whole original prompt plus
424
+ `RÄTTNINGAR:` with the error lines for just that spec and the line
425
+ `Returnera endast spec <N>`; the reply replaces only that directory's draft.
426
+ **If a defect remains after redispatch, the draft never becomes `spec.md`:** the
427
+ second draft is also archived as `spec-avvisad-<datum>.md` with its own
428
+ runs row, the directory stands without `spec.md` and keeps the status `spec_draft`,
429
+ and in `utvärdera` the run becomes `"ofärdig"`. Show the defects and stop.
430
+ **A candidate is stopped here, never in bedöm.** If precheck writes the line
431
+ `KANDIDAT: KONCEPT` or `KANDIDAT: DIAGNOS` (the spec carries the marker line from
432
+ the spec format's candidate form), run directly
394
433
  `node <skillkatalog>/scripts/archive.mjs stopp <katalog> --utfall <utfallet ur raden> --kalla spec.md`
395
- på den katalogen: en kandidat har inget analys- eller poängsteg att vänta
396
- på, och `stopp`-fältet är det som gör utfallet synligt för `bluf`,
397
- rapporten och `set-status`. Kandidatens katalog kan fortfarande läggas på
398
- brädan i steg 7 (kunddialogen är nästa steg för den), men `bedöm` hoppar
399
- över den.
400
- **Prisavgörande öppet val stoppas här (ADR-0118).** Skriver precheck raden
401
- `PRISAVGORANDE: <valet>` (granskarblockets rad `Prisavgörande öppet val:
402
- ja: …`), kör `archive.mjs stopp <katalog> --utfall TILLBAKA_TILL_SPEC
403
- --kalla granskarblock.md` och visa valet i kundens språk; ingen assessor.
404
- Är `state.json.respec_varv` redan ≥ 1 kör i stället `--utfall ESKALERA
434
+ on that directory: a candidate has no analysis or scoring step to wait
435
+ for, and the `stopp` field is what makes the outcome visible to `bluf`,
436
+ the report and `set-status`. The candidate's directory can still be placed on
437
+ the board in step 7 (the customer dialogue is the next step for it), but `bedöm` skips
438
+ it.
439
+ **A price-decisive open choice is stopped here (ADR-0118).** If precheck writes the line
440
+ `PRISAVGORANDE: <valet>` (the reviewer block's row `Price-decisive open choice: yes: …`),
441
+ run `archive.mjs stopp <katalog> --utfall TILLBAKA_TILL_SPEC
442
+ --kalla granskarblock.md` and show the choice in the customer's language; no assessor.
443
+ If `state.json.respec_varv` is already ≥ 1, run instead `--utfall ESKALERA
405
444
  --kalla granskarblock.md --orsak "prisavgörande val kvarstår efter respec:
406
- <valet>"`: en människa dömer, posten går aldrig en tredje specrunda. När
407
- valet avgjorts skrivs svaret under `## Avgjorda val` i manifestet, sedan
408
- `respec` och ny specifier-dispatch.
409
- BRISTER i `state.json` är wrapperns egna filer och
410
- rättas av wrappern själv, aldrig via omdispatch. VARNING:ar visas men
411
- stoppar inget.
412
- 7. **Svara BLUF:** en rad per spec (rubrik + ev. flaggor), sedan granskarblocket
413
- i sin helhet. Erbjud att lägga specarna på brädan via request-sync (🔍) — gör
414
- det bara på uttrycklig begäran. När en post läggs på brädan: sätt status
415
- `spec_synced`, döp om slug-katalogen till det myntade gid:t, notera sluggen
416
- i `former_slug` och uppdatera `request_id` i `state.json`.
417
- **Avsluta enligt `## Avslutning: var posten står`** (tabellen ur
418
- `archive.mjs nasta-steg`, brädraden, ISÄR-kontrollen), aldrig utan.
419
-
420
- ## Uppgift: `bedöm`
421
-
422
- Kör prissättningsbenet: påverkansanalys om den saknas, sedan poängsättning med
423
- driftvakt.
424
-
425
- 1. **Grind — obligatorisk, aldrig överhoppbar.** Bedömning kräver en granskad
426
- spec, och godkännandet bor i Asana:
427
- - **Posten har gid:** läs Offer status via request-sync (huvudkonversationen)
428
- omedelbart före körningen — aldrig från minnet eller en tidigare listning.
429
- Endast `✅ Spec ok` (normaliserad på alfabetiska kärnan) släpper igenom.
430
- Annan status: visa den och stanna. En inklistrad spec för en post som har
431
- gid räknas inte — det är brädstatusen som gäller, läs den ändå.
432
- - **Posten saknar gid MEN repot har `.haus-workflow/request.json`:** vägra — repot är
433
- kopplat till en bräda, så en gid-lös post ska in via intaget och granskas
434
- där, inte prissättas vid sidan av. Grinden är värd något bara om den inte
435
- går att kliva runt.
436
- - **Posten saknar gid OCH repot saknar brädkoppling:** fråga uttryckligen
437
- "Är specen godkänd av en människa — och av vem?" via AskUserQuestion och
438
- kör bara på ja med namn. Anta aldrig, och låt aldrig "specen ser klar ut"
439
- eller ett tidigare samtal ersätta frågan.
440
- - **Posten har gid men saknar lokal arkivkatalog:** stöds inte ännu
441
- (hydrering från brädan kommer med batch-steget) — säg det och stanna;
442
- granskarblocket går inte att återskapa ur Asana.
443
- - Anteckna passerad grind i `state.json.approval` (manual-fallet med `by`).
444
- Nästa `bedöm` läser om statusen igen.
445
- 2. **Formkontroll.** Kör `node <skillkatalog>/scripts/precheck.mjs <katalog> --impact`
446
- (utan `--impact` om analysen ännu inte finns). En `spec.md` med mer än ett
447
- specblock är en BRIST — dela posten enligt arkivregeln innan något dispatchas.
448
- Övriga BRISTER → åtgärda ledet som brister (om spec: tillbaka till
449
- speca-steget) innan något dyrt dispatchas.
450
- **`KANDIDAT:`-raden avgör före allt annat.** Skriver precheck
451
- `KANDIDAT: KONCEPT` eller `KANDIDAT: DIAGNOS` (raden kommer alltid, även
452
- vid exit 1) är posten en kandidat: hoppa över steg 3 till 7, kör
445
+ <valet>"`: a human judges, the item never goes a third spec round. When
446
+ the choice has been decided, the answer is written under `## Avgjorda val` in the manifest, then
447
+ `respec` and a new specifier dispatch.
448
+ BRISTER in `state.json` are the wrapper's own files and
449
+ are fixed by the wrapper itself, never via redispatch. VARNING lines are shown but
450
+ stop nothing.
451
+ 7. **Reply BLUF:** one line per spec (heading + any flags), then the reviewer block
452
+ in its entirety. Offer to place the specs on the board via request-sync (🔍); do
453
+ it only on explicit request. When an item is placed on the board: set status
454
+ `spec_synced`, rename the slug directory to the minted gid, note the slug
455
+ in `former_slug` and update `request_id` in `state.json`.
456
+ **Close per `## Closing: where the item stands`** (the table from
457
+ `archive.mjs nasta-steg`, the board line, the ISÄR check), never without.
458
+
459
+ ## Task: `bedöm`
460
+
461
+ Runs the pricing leg: an impact analysis if it is missing, then scoring with a
462
+ drift guard.
463
+
464
+ 1. **Gate: mandatory, never skippable.** Assessment requires a reviewed
465
+ spec, and the approval lives in Asana:
466
+ - **The item has a gid:** read Offer status via request-sync (the main conversation)
467
+ immediately before the run, never from memory or an earlier listing.
468
+ Only `✅ Spec ok` (normalized on the alphabetic core) lets it through.
469
+ Any other status: show it and stop. A pasted spec for an item that has a
470
+ gid does not count: it is the board status that holds, read it anyway.
471
+ - **The item lacks a gid BUT the repo has `.haus-workflow/request.json`:** refuse: the repo is
472
+ connected to a board, so a gid-less item must come in through the intake and be reviewed
473
+ there, not priced on the side. The gate is worth something only if it cannot
474
+ be stepped around.
475
+ - **The item lacks a gid AND the repo lacks a board connection:** ask explicitly
476
+ "Has the spec been approved by a human, and by whom?" via AskUserQuestion and
477
+ run only on yes with a name. Never assume, and never let "the spec looks done"
478
+ or an earlier conversation replace the question.
479
+ - **The item has a gid but lacks a local archive directory:** not supported yet
480
+ (hydration from the board comes with the batch step); say so and stop;
481
+ the reviewer block cannot be recreated from Asana.
482
+ - Note the passed gate in `state.json.approval` (the manual case with `by`).
483
+ The next `bedöm` reads the status again.
484
+ 2. **Form check.** Run `node <skillkatalog>/scripts/precheck.mjs <katalog> --impact`
485
+ (without `--impact` if the analysis does not exist yet). A `spec.md` with more than one
486
+ spec block is a BRIST: split the item per the archive rule before anything is dispatched.
487
+ Other BRISTER → fix the link that is defective (if the spec: back to the
488
+ speca step) before anything expensive is dispatched.
489
+ **The `KANDIDAT:` line decides before everything else.** If precheck writes
490
+ `KANDIDAT: KONCEPT` or `KANDIDAT: DIAGNOS` (the line always comes, even
491
+ at exit 1), the item is a candidate: skip steps 3 to 7, run
453
492
  `archive.mjs stopp <katalog> --utfall <utfallet ur raden> --kalla spec.md`
454
- om `stopp` inte redan är satt (speca steg 6 brukar ha gjort det;
455
- ett identiskt omanrop är ofarligt), och routa posten som stopputfallet
456
- nedan. Ingen analys och ingen poäng får någonsin dispatchas på en
457
- kandidat.
458
- 3. **Analys.** Saknas `impact.md`, eller fäller precheck den på
459
- underlagsproveniens (analysen gjordes mot en annan spec): kräver repo i
460
- sessionen (annars stopp — en gissad analys är förbjuden av skillen själv).
461
- Dispatcha `request-assessor` enligt promptmallen — specens granskarblock
462
- skickas med som internt underlag (samma förtroendedomän; flaggorna där är
463
- ofta analysens viktigaste ingång). Promptens `KÄLLOR:`-rad bär
464
- `kallmanifest.md` ur katalogen ordagrant, eller `inga` när filen saknas,
465
- och `SPRÅK:`-raden bär manifestets `SPRÅK:`-värde (#521: kundtextens
466
- språk gissas aldrig om, samma rad går till scorern i steg 4);
467
- assessorn verifierar manifestet själv och ska aldrig ta det för sant
468
- (ADR-0113: assessorn utforskade repot från noll trots att manifestet redan
469
- fanns). Arkivera svaret som `impact.md`
470
- (inklusive dess "## Till granskaren"-block — visa blocket för användaren,
471
- aldrig till kundfält), stämpla med
493
+ if `stopp` is not already set (speca step 6 has usually done it;
494
+ an identical repeat call is harmless), and route the item as the stop outcome
495
+ below. No analysis and no score may ever be dispatched on a
496
+ candidate.
497
+ 3. **Analysis.** If `impact.md` is missing, or precheck rejects it on
498
+ source-material provenance (the analysis was made against another spec): this requires a repo in
499
+ the session (otherwise stop: a guessed analysis is forbidden by the skill itself).
500
+ Dispatch `request-assessor` per the prompt template: the spec's reviewer block
501
+ is sent along as internal source material (the same trust domain; the flags there are
502
+ often the analysis's most important input). The prompt's `KÄLLOR:` line carries
503
+ `kallmanifest.md` from the directory verbatim, or `inga` when the file is missing,
504
+ and the `SPRÅK:` line carries the manifest's `SPRÅK:` value (#521: the customer text's
505
+ language is never guessed again, the same line goes to the scorer in step 4);
506
+ the assessor verifies the manifest itself and must never take it as true
507
+ (ADR-0113: the assessor explored the repo from zero even though the manifest already
508
+ existed). Archive the reply as `impact.md`
509
+ (including its "## For the reviewer" block: show the block to the user,
510
+ never to customer fields), stamp with
472
511
  `node <skillkatalog>/scripts/archive.mjs add-run <katalog> --file impact.md
473
512
  --skill request-assess-impact --agent request-assessor
474
- --model <ur MODELL-svansraden>` (scriptet sätter `underlag_sha256` mot
475
- `spec.md`). Kör precheck igen med `--impact`. Läs sedan raden
476
- `AVVIKELSE: <etikett>` ur precheck-utskriften (etiketten på analysens rad
477
- `**Avvikelse mot specen:**`; inte att förväxla med agenternas svansrad):
478
- `motsägelse` betyder att specen förutsätter något som inte stämmer i
479
- systemet, och då kör
513
+ --model <ur MODELL-svansraden>` (the script sets `underlag_sha256` against
514
+ `spec.md`). Run precheck again with `--impact`. Then read the line
515
+ `AVVIKELSE: <etikett>` from the precheck output (the label on the analysis's row
516
+ `**Deviation from the spec:**`; not to be confused with the agents' tail line):
517
+ `motsägelse` means that the spec presupposes something that does not hold in
518
+ the system, and then run
480
519
  `node <skillkatalog>/scripts/archive.mjs stopp <katalog> --utfall TILLBAKA_TILL_SPEC --kalla impact.md`
481
- (kommandot sätter status `assessed`), hoppa över steg 4 till 7 och routa
482
- posten som stopputfallet nedan: ingen scorer dispatchas på ett underlag
483
- som redan säger att specen måste tillbaka. `ingen` och `precisering` går
484
- vidare till steg 4. Fäller precheck det nya
485
- utkastet → omdispatcha assessorn EN gång enligt omdispatch-regeln, och ge
486
- det förkastade utkastet kvitto precis som i speca steg 6: arkivera det som
487
- `impact-avvisad-<datum>.md` och stämpla en egen runs-rad med kontrollens
488
- felrader i `--note` (ADR-0099).
489
- 4. **Prissättning.** Dispatcha `request-scorer` enligt promptmallen.
490
- Ingen prislista, inget repo, inga andra posters dokument i prompten.
491
- 5. **Ankarlösning och kontraktskontroll.** Skriv agentens hela svarstext (JSON
492
- plus svansrader) till fil och kör
520
+ (the command sets status `assessed`), skip steps 4 to 7 and route
521
+ the item as the stop outcome below: no scorer is dispatched on source material
522
+ that already says the spec must go back. `ingen` and `precisering` go
523
+ on to step 4. If precheck rejects the new
524
+ draft → redispatch the assessor ONCE per the redispatch rule, and give
525
+ the rejected draft a receipt exactly as in speca step 6: archive it as
526
+ `impact-avvisad-<datum>.md` and stamp a runs row of its own with the check's
527
+ error lines in `--note` (ADR-0099).
528
+ 4. **Pricing.** Dispatch `request-scorer` per the prompt template.
529
+ No price list, no repo, no other items' documents in the prompt.
530
+ 5. **Anchor resolution and contract check.** Write the agent's whole reply text (JSON
531
+ plus tail lines) to a file and run
493
532
  `node <skillkatalog>/scripts/kontroll.mjs <svar.txt> <spec.md> <impact.md> --expect-id <katalogens id> --ut <svar.json> --fixa-kundtext --state <katalog>/state.json --sprak <svenska|engelska>`.
494
- `--state` ger contract-check split-status: `OVER_SKALAN` på något som inte
495
- är en split-del fälls, och ett inget-tak-utfall utan steg mot A16 fälls
496
- (#503). `--sprak` ger contract-check kundtextens språk, samma värde som
497
- promptens `SPRÅK:`-rad (ur `experiment.json` i utvärdera, ur `SPRÅK:` i
498
- `kallmanifest.md` här): kundtext med minst två funktionsord ur fel språk
499
- fälls som BROTT och går till omdispatch, aldrig till kundtextfixen, som
500
- byter ord men inte översätter (#521).
501
- Scriptet strippar svansraderna (`MODELL:`, `LÄSTA:`, ev. `AVVIKELSE:`),
502
- reparerar det dispatchern redan vet (`request_id`, enumform,
503
- siffersträngar, LÄSTA-radens sökvägsform) och kör check-lasta,
504
- `resolve-anchors.mjs` (beläggsankare till ordagranna källblock;
505
- OLÖST/FLERTYDIG är ett kontraktsbrott) och contract-check i en process; ett
506
- protokollterm-brott i ett rent kundtextfält skrivs om av en billig modell i
507
- stället för att kosta en ny scorerkörning. `REPARERAT:`-raderna visas i
508
- BLUF-svaret och skrivs i runs-radens `--note`.
509
- `impact.md` skickas ALLTID med till contract-check, aldrig bara specen:
510
- scriptet läser etiketten på `**Avvikelse mot specen:**` och fäller varje
511
- annat utfall än TILLBAKA_TILL_SPEC när analysen bär `motsägelse`
512
- (motsägelsegrinden i scriptet är bältet, steg 3 hängslena; en post som
513
- ändå nått hit har ett underlag scriptet ska få se). Samma script kräver
514
- att varje `bracket.steps[i]` är exakt `{anchor, relation, grund}`.
515
- BROTT som kvarstår efter reparationerna → omdispatch EN gång enligt
516
- omdispatch-regeln, med felraderna i prompten. Kvarstår brott: visa dem och
517
- stanna utan att arkivera svaret som giltigt varv, men släng det inte:
518
- arkivera som `score-pricing-vN-avvisad.json` (utanför N-räkningen) med
519
- kontrollens felrader i en runs-rad, så även misslyckade körningar har
520
- kvitto. En `AVVIKELSE:`-rad visas alltid för användaren.
521
- 6. **Dubbelkörning (driftvakt).** Körning 2 startar ALLTID samtidigt med
522
- körning 1, med samma prompt och i en ny isolerad körning; i produktion
523
- dispatchas båda Agent-anropen på en gång (#500, ADR-0113), promptarna är
524
- identiska och körningarna oberoende. Trigger: poängen är ≥ 1 (ADR-0098),
525
- eller utfallet är `OVER_SKALAN` (ADR-0116: det utfallet skickar posten
526
- till manuell offert och är prisbärande; två OVER_SKALAN med olika golv är
527
- OLIKA och går till dom). Övriga stopputfall dubbelkörs inte: DELA_UPP körs
528
- om per del, KONCEPT/DIAGNOS/TILLBAKA_TILL_SPEC går till människa ändå.
529
- Tröskeln avgör bara om paret RÄKNAS som dubbelkörning, aldrig om
530
- körning 2 startar. Blir körning 1 ett stopputfall utom OVER_SKALAN, eller
531
- poäng under 1, arkiveras körning 2 som `-run2` med `superseded_by` mot
532
- huvudfilen och en notering om att den inte krävdes, den räknas aldrig som
533
- dubbelkörning, och stopproutningen nedan gäller som förut. I övriga fall
534
- jämförs paret som nedan; skillens eget acceptanstest är att två oberoende
535
- körningar ger samma resultat.
536
- - Kör hela steg 5 (ankarlösning, sedan kontraktskontroll) på det nya
537
- svaret först, med egen omdispatch-budget (EN).
538
- - Jämför sedan deterministiskt:
533
+ `--state` gives contract-check the split status: `OVER_SKALAN` on something that is not
534
+ a split part is rejected, and a no-ceiling outcome without a step against A16 is rejected
535
+ (#503). `--sprak` gives contract-check the customer text's language, the same value as
536
+ the prompt's `SPRÅK:` line (from `experiment.json` in utvärdera, from `SPRÅK:` in
537
+ `kallmanifest.md` here): customer text with at least two function words from the wrong language
538
+ is rejected as BROTT and goes to redispatch, never to the customer-text fix, which
539
+ swaps words but does not translate (#521).
540
+ The script strips the tail lines (`MODELL:`, `LÄSTA:`, any `AVVIKELSE:`),
541
+ repairs what the dispatcher already knows (`request_id`, enum form,
542
+ digit strings, the LÄSTA line's path form) and runs check-lasta,
543
+ `resolve-anchors.mjs` (evidence anchors to verbatim source blocks;
544
+ OLÖST/FLERTYDIG is a contract breach) and contract-check in one process; a
545
+ protocol-term breach in a pure customer-text field is rewritten by a cheap model
546
+ instead of costing a new scorer run. The `REPARERAT:` lines are shown in
547
+ the BLUF reply and written into the runs row's `--note`.
548
+ `impact.md` is ALWAYS sent to contract-check, never only the spec:
549
+ the script reads the label on `**Deviation from the spec:**` and rejects every
550
+ outcome other than TILLBAKA_TILL_SPEC when the analysis carries `contradiction`
551
+ (the contradiction gate in the script is the belt, step 3 the braces; an item that
552
+ has still reached this point has source material the script must get to see). The same script requires
553
+ every `bracket.steps[i]` to be exactly `{anchor, relation, grund}`.
554
+ BROTT that remain after the repairs → redispatch ONCE per
555
+ the redispatch rule, with the error lines in the prompt. If breaches remain: show them and
556
+ stop without archiving the reply as a valid round, but do not throw it away:
557
+ archive it as `score-pricing-vN-avvisad.json` (outside the N count) with
558
+ the check's error lines in a runs row, so even failed runs have a
559
+ receipt. An `AVVIKELSE:` line is always shown to the user.
560
+ 6. **Double run (drift guard).** Run 2 ALWAYS starts at the same time as
561
+ run 1, with the same prompt and in a new isolated run; in production
562
+ both Agent calls are dispatched at once (#500, ADR-0113), the prompts are
563
+ identical and the runs independent. Trigger: the score is ≥ 1 (ADR-0098),
564
+ or the outcome is `OVER_SKALAN` (ADR-0116: that outcome sends the item
565
+ to a manual offer and is price-bearing; two OVER_SKALAN with different floors are
566
+ OLIKA and go to a ruling). Other stop outcomes are not double-run: DELA_UPP is
567
+ rerun per part, KONCEPT/DIAGNOS/TILLBAKA_TILL_SPEC go to a human anyway.
568
+ The threshold only decides whether the pair COUNTS as a double run, never whether
569
+ run 2 starts. If run 1 becomes a stop outcome other than OVER_SKALAN, or
570
+ a score below 1, run 2 is archived as `-run2` with `superseded_by` against
571
+ the main file and a note that it was not required, it never counts as a
572
+ double run, and the stop routing below applies as before. In other cases
573
+ the pair is compared as below; the skill's own acceptance test is that two independent
574
+ runs give the same result.
575
+ - Run the whole of step 5 (anchor resolution, then contract check) on the new
576
+ reply first, with its own redispatch budget (ONE).
577
+ - Then compare deterministically:
539
578
  `node <skillkatalog>/scripts/compare-scores.mjs <poäng1.json> <poäng2.json>`.
540
- Scriptet diffar även de inre stegen: samma ankare i `bracket.steps` läst
541
- som `storre` i ena körningen och `mindre` i den andra är en hård diffrad
542
- (OLIKA), aldrig en toleransrad. Golv- och takankaren är undantagna, de
543
- flippar av konstruktion när banden angränsar och ägs av toleransregeln.
544
- - **LIKA** → körning 1 gäller: `double_run: "bekräftad"` på dess
545
- runs-rad. Körning 2-filen arkiveras som `score-pricing-vN-run2.json`
546
- med SAMMA N och `superseded_by` satt till körning 1-filen.
547
- `-run2`-filer räknas ALDRIG när nästa N bestäms, och guard och
548
- `detaljer` läser alltid filen utan suffix.
549
- - **OLIKA** → domen är människans, aldrig maskinens. Arkivera båda
550
- körningarna (körning 1 utan suffix, körning 2 som `-run2`; på båda
551
- raderna `double_run: "olika"` med `superseded_by: null`).
552
- Status `assessed`. Ingen kundtext får gå till
553
- request-sync medan spärren står (`archive.mjs sparr` är tolken).
554
- Visa i BLUF-svaret beläggen bakom oenigheten, sida vid sida:
555
- diffraderna (outcome, score, ankare, konfidens) har ingen
556
- radkoppling till beläggen, `drivers[]` är en egen lista, så visa
557
- samtliga `drivers[].citat`/`pastaende` ur båda körningarna
558
- som stöd för hela utfallet. Det var beläggen som avgjorde domen i
559
- varje uppmätt äkta oenighet. Hänvisa sedan till `döm <post>` och
560
- STANNA: ingen omkörning, ingen eskalering, inget val av körning.
561
- - **LIKA_TOLERANS** (poängskillnad ≤ 0,5 med samma outcome, oavsett
562
- konfidens, en konfidensskillnad ensam fäller aldrig ett par inom
563
- toleransen) → paret räknas som bekräftat. Verdiktradens utpekade
564
- körning (högsta poängen) blir huvudfilen `score-pricing-vN.json`,
565
- och den andra arkiveras som `-run2` med `superseded_by` satt mot
566
- huvudfilen, oavsett vilken av dem som var körning 1. Det är
567
- avsiktligt: båda körningarna har passerat kontraktskontrollen och
568
- skillnaden ligger inom toleransen. Sätt `double_run: "tolerans"` på
569
- bägge runs-raderna. Toleransen är en medveten prisregel: högsta
570
- värdet skyddar marginalen, och ett medelvärde räknas fortfarande
571
- aldrig fram. Visa toleransraderna i BLUF-svaret. Ett par av två
572
- `OVER_SKALAN` med SAMMA golv där bara konfidensen skiljer skriver
573
- också `LIKA_TOLERANS`, men utan poäng att jämföra (OVER_SKALAN bär
574
- `score: null`) vinner körning 1 deterministiskt, aldrig "högsta
575
- poängen" (ADR-0116).
576
- - Exit 2 från jämförelsescriptet är ett dispatcherfel (ojämförbara
577
- körningar) — visa felet och stanna, rapportera aldrig LIKA.
578
- 7. **Arkivera** godkända JSON-svar. Hämta varvets N med
579
+ The script also diffs the inner steps: the same anchor in `bracket.steps` read
580
+ as `storre` in one run and `mindre` in the other is a hard diff row
581
+ (OLIKA), never a tolerance row. The floor and ceiling anchors are exempt, they
582
+ flip by construction when the bands are adjacent and are owned by the tolerance rule.
583
+ - **LIKA** → run 1 holds: `double_run: "bekräftad"` on its
584
+ runs row. The run 2 file is archived as `score-pricing-vN-run2.json`
585
+ with the SAME N and `superseded_by` set to the run 1 file.
586
+ `-run2` files are NEVER counted when the next N is determined, and guard and
587
+ `detaljer` always read the file without a suffix.
588
+ - **OLIKA** → the ruling is the human's, never the machine's. Archive both
589
+ runs (run 1 without a suffix, run 2 as `-run2`; on both
590
+ rows `double_run: "olika"` with `superseded_by: null`).
591
+ Status `assessed`. No customer text may go to
592
+ request-sync while the hold stands (`archive.mjs sparr` is the interpreter).
593
+ Show in the BLUF reply the evidence behind the disagreement, side by side:
594
+ the diff rows (outcome, score, anchor, confidence) have no
595
+ row link to the evidence, `drivers[]` is a list of its own, so show
596
+ all `drivers[].citat`/`pastaende` from both runs
597
+ as support for the whole outcome. It was the evidence that decided the ruling in
598
+ every measured genuine disagreement. Then refer to `döm <item>` and
599
+ STOP: no rerun, no escalation, no choice of run.
600
+ - **LIKA_TOLERANS** (a score difference ≤ 0.5 with the same outcome, regardless of
601
+ confidence, a confidence difference alone never rejects a pair within
602
+ the tolerance) → the pair counts as confirmed. The run the verdict line points out
603
+ (the highest score) becomes the main file `score-pricing-vN.json`,
604
+ and the other one is archived as `-run2` with `superseded_by` set against
605
+ the main file, regardless of which of them was run 1. That is
606
+ deliberate: both runs have passed the contract check and
607
+ the difference lies within the tolerance. Set `double_run: "tolerans"` on
608
+ both runs rows. The tolerance is a deliberate pricing rule: the highest
609
+ value protects the margin, and an average is still never
610
+ computed. Show the tolerance rows in the BLUF reply. A pair of two
611
+ `OVER_SKALAN` with the SAME floor where only the confidence differs
612
+ also writes `LIKA_TOLERANS`, but with no score to compare (OVER_SKALAN carries
613
+ `score: null`) run 1 wins deterministically, never "the highest
614
+ score" (ADR-0116).
615
+ - Exit 2 from the comparison script is a dispatcher error (incomparable
616
+ runs): show the error and stop, never report LIKA.
617
+ 7. **Archive** the approved JSON replies. Fetch the round's N with
579
618
  `node <skillkatalog>/scripts/archive.mjs next-n <katalog> --typ score`,
580
- skriv filerna (`score-pricing-vN.json`, körning 2
581
- som `-run2`, avvisade som `-avvisad`), och stämpla VARJE körning med
582
- `archive.mjs add-run` (flaggor för fil, skill, agent, modell ur
583
- MODELL-svansraden som förut, referens-hash via `--ref` för skillens
584
- referensfil (se runs-kontraktet), double-run-utfall, superseded-by,
585
- partial-rerun och pairs-with där de gäller). Effort läser scriptet självt
586
- ur agentdefinitionen under Claude-konfigkatalogen när `--effort`
587
- utelämnas, så utelämna flaggan; `--model` förblir obligatorisk och kommer
588
- ur svansraden, aldrig ur definitionen (under `utvärdera` körs agenterna
589
- med override, så definitionens modell är per konstruktion inte den som
590
- kördes). Exempel för huvudfilen vid ett bekräftat par:
619
+ write the files (`score-pricing-vN.json`, run 2
620
+ as `-run2`, rejected ones as `-avvisad`), and stamp EVERY run with
621
+ `archive.mjs add-run` (flags for file, skill, agent, model from
622
+ the MODELL tail line as before, reference hash via `--ref` for the skill's
623
+ reference file (see the runs contract), double-run outcome, superseded-by,
624
+ partial-rerun and pairs-with where they apply). The script reads the effort itself
625
+ from the agent definition under the Claude config directory when `--effort`
626
+ is omitted, so omit the flag; `--model` stays mandatory and comes
627
+ from the tail line, never from the definition (under `utvärdera` the agents run
628
+ with an override, so the definition's model is by construction not the one that
629
+ ran). Example for the main file of a confirmed pair:
591
630
  `node <skillkatalog>/scripts/archive.mjs add-run <katalog>
592
631
  --file score-pricing-v1.json --skill request-score
593
632
  --agent request-scorer --model <ur MODELL-svansraden>
594
633
  --ref poangskala.md=<hash via
595
634
  ref-hash.mjs på request-score/references/poangskala.md>
596
- --double-run bekräftad`. `--double-run` tar ENDAST de tre
597
- literalerna `bekräftad`, `olika` eller `tolerans` (scriptet vägrar allt
598
- annat, flaggnamnet med bindestreck, aldrig `--double_run` med understreck
599
- som i JSON-fältet). Utelämna flaggan helt när ingen dubbelkörning gjorts.
600
- `--superseded-by <fil>` och `--partial-rerun` (utan värde) läggs till där
601
- de gäller, se runs-kontraktet. Stämplingsreglerna
602
- bor i scriptet och dess tester, inte här. Sätt status via
603
- `archive.mjs set-status <katalog> priced` (scriptet vägrar vid spärr,
604
- stopputfall och experiment — se runs-kontraktet)
605
- eller `assessed`.
606
- 8. **Svara BLUF:** tabellen kommer ur
607
- `node <skillkatalog>/scripts/archive.mjs bluf <katalog>` och klistras in
608
- som den är (`| post | poäng | utfall | konfidens | anm |`, en rad per
609
- katalog och del; poängen ur filerna, stopputfall ur `stopp`, OLIKA och
610
- tolerans i `anm`), ALDRIG handskriven: en handskriven tabell
611
- felrapporterade i skarp körning. Under tabellen en rad
612
- per avvikelse värd att se (ESKALERA, DELA_UPP,
613
- INPUT_SAKNAS, AVVIKELSE-rader). JSON:en visas inte i chatten —
614
- `detaljer <post>` gör det. Skriv-tillbaka till brädan sker via request-sync
615
- i huvudkonversationen, bara på uttrycklig begäran — och det är den flytten
616
- som tar posten ✅ Spec ok → ⚖️ Poängsättning redo för granskning enligt
617
- syncens regler. **Avsluta enligt `## Avslutning: var posten står`**
618
- (tabellen ur `archive.mjs nasta-steg`, brädraden, ISÄR-kontrollen, och
619
- stopputfall eller OLIKA som ett val användaren ska ta), aldrig utan.
620
-
621
- **Stopputfall routas, aldrig tolkas.** Ett stopputfall arkiveras som varvets
622
- fil (steg 7), statusen blir `assessed`, aldrig `priced`, och posten routas:
623
-
624
- - `DELA_UPP` → föreslå ny analysrunda per del; delarna blir egna kataloger med
625
- `split` satt (`part`/`of`), och en del utan tak blir `OVER_SKALAN`, aldrig
626
- DELA_UPP igen. Delposter ärver enligt skillen postens spec och skriver aldrig
627
- en egen: förälderns `spec.md` och `granskarblock.md` KOPIERAS oförändrade in
628
- i varje delkatalog, var för sig (ett granskarblock per delkatalog, ADR-0118,
629
- ingen delkatalog delar sitt block med en annan), medan `impact.md` skrivs per
630
- del av den nya analysrundan.
631
- - `OVER_SKALAN` → posten offereras separat; delas aldrig vidare (delningen görs
632
- ett steg, skillens egen regel).
633
- - `TILLBAKA_TILL_SPEC` / `KONCEPT` / `DIAGNOS` → tillbaka till spec-steget
634
- respektive kunddialog; visa skälet. Kom utfallet ur scripten (kandidat i
635
- `spec.md`, `motsägelse` i `impact.md`, prisavgörande öppet val i
636
- `granskarblock.md`, ADR-0118) bär `state.json.stopp` det via
637
- `archive.mjs stopp` och det finns ingen varvsfil; kom det ur scorern
638
- arkiveras varvsfilen som förut och `stopp` förblir `null` (kommandot tar
639
- bara de tre scriptbevisade källorna: `spec.md`, `impact.md`,
640
- `granskarblock.md`). En respec nollar `stopp`.
641
- - `ESKALERA` / `INPUT_SAKNAS` → visa skälet och stanna, eller ett prisavgörande
642
- val som kvarstår efter respec (källa `granskarblock.md`, orsak i
635
+ --double-run bekräftad`. `--double-run` takes ONLY the three
636
+ literals `bekräftad`, `olika` or `tolerans` (the script refuses everything
637
+ else; the flag name with a hyphen, never `--double_run` with an underscore
638
+ as in the JSON field). Omit the flag entirely when no double run has been done.
639
+ `--superseded-by <fil>` and `--partial-rerun` (without a value) are added where
640
+ they apply, see the runs contract. The stamping rules
641
+ live in the script and its tests, not here. Set the status via
642
+ `archive.mjs set-status <katalog> priced` (the script refuses on a hold,
643
+ a stop outcome and an experiment; see the runs contract)
644
+ or `assessed`.
645
+ 8. **Reply BLUF:** the table comes from
646
+ `node <skillkatalog>/scripts/archive.mjs bluf <katalog>` and is pasted
647
+ as it is (`| post | poäng | utfall | konfidens | anm |`, one row per
648
+ directory and part; the score from the files, stop outcomes from `stopp`, OLIKA and
649
+ tolerance in `anm`), NEVER handwritten: a handwritten table
650
+ misreported in a live run. Under the table, one line
651
+ per deviation worth seeing (ESKALERA, DELA_UPP,
652
+ INPUT_SAKNAS, AVVIKELSE lines). The JSON is not shown in the chat:
653
+ `detaljer <item>` does that. Writing back to the board happens via request-sync
654
+ in the main conversation, only on explicit request, and it is that move
655
+ that takes the item ✅ Spec ok → ⚖️ Poängsättning redo för granskning per
656
+ the sync's rules. **Close per `## Closing: where the item stands`**
657
+ (the table from `archive.mjs nasta-steg`, the board line, the ISÄR check, and
658
+ a stop outcome or OLIKA as a choice the user must make), never without.
659
+
660
+ **Stop outcomes are routed, never interpreted.** A stop outcome is archived as the round's
661
+ file (step 7), the status becomes `assessed`, never `priced`, and the item is routed:
662
+
663
+ - `DELA_UPP` → propose a new analysis round per part; the parts become directories of their own with
664
+ `split` set (`part`/`of`), and a part without a ceiling becomes `OVER_SKALAN`, never
665
+ DELA_UPP again. Sub-items inherit, per the skill, the item's spec and never write
666
+ one of their own: the parent's `spec.md` and `granskarblock.md` are COPIED unchanged into
667
+ each part directory, separately (one reviewer block per part directory, ADR-0118,
668
+ no part directory shares its block with another), while `impact.md` is written per
669
+ part by the new analysis round.
670
+ - `OVER_SKALAN` → the item is quoted separately; never split further (the split is done
671
+ one step, the skill's own rule).
672
+ - `TILLBAKA_TILL_SPEC` / `KONCEPT` / `DIAGNOS` → back to the spec step
673
+ or the customer dialogue respectively; show the reason. If the outcome came from the scripts (a candidate in
674
+ `spec.md`, `contradiction` in `impact.md`, a price-decisive open choice in
675
+ `granskarblock.md`, ADR-0118), `state.json.stopp` carries it via
676
+ `archive.mjs stopp` and there is no round file; if it came from the scorer,
677
+ the round file is archived as before and `stopp` stays `null` (the command takes
678
+ only the three script-proven sources: `spec.md`, `impact.md`,
679
+ `granskarblock.md`). A respec resets `stopp`.
680
+ - `ESKALERA` / `INPUT_SAKNAS` → show the reason and stop, or a price-decisive
681
+ choice that remains after respec (source `granskarblock.md`, reason in
643
682
  `stopp.orsak`, ADR-0118).
644
683
 
645
- ## Uppgift: `status`
646
-
647
- Kör request-sync:s kö-läsning i huvudkonversationen för lägena
648
- 📥 Inkommet, 🔍 Spec redo för granskning, ✅ Spec ok och ⚖️ Poängsättning redo
649
- för granskning. Svara som BLUF-tabell med **radnummer**: `nr · post · Offer
650
- status`. Spara nr → gid-mappningen i sessionen (gid behöver inte visas i
651
- tabellen) — numren är vad `godkänn`/`underkänn`/`bedöm`/`detaljer` refererar
652
- till, och varje verb slår upp och agerar på **gid**, aldrig på numret självt.
653
- Ingen JSON i chatten.
654
-
655
- ## Uppgifter: `godkänn` / `underkänn`
656
-
657
- Genvägar till samma spak som i Asana — **Offer status på brädan förblir enda
658
- beslutet**. Verben gör beslutet snabbare att verkställa, aldrig lättare att
659
- missta:
660
-
661
- - **Adressering:** radnummer ur senaste `status`-listningen i samma session
662
- (slås upp till gid via den sparade mappningen), eller ett gid direkt. ALDRIG
663
- fuzzy rubrikmatchning — fel post godkänd är värre än ett stopp. Finns ingen
664
- färsk listning och inget gid: kör `status` först.
665
- - **En post per verb-anrop.** "Godkänn 2 och 4" är två anrop i följd, vart och
666
- ett läser och flyttar sin post. Aldrig batch-godkännande på "allt ser bra ut".
667
- - **Lägesgrind för båda verben:** läs postens nuvarande Offer status via
668
- request-sync först. Verben verkar bara från gransknings-lägena
669
- (🔍 Spec redo för granskning, ⚖️ Poängsättning redo för granskning). Står
670
- posten någon annanstans: visa status och stanna i stället för att gissa.
671
- - **`godkänn <nr|gid>`:** 🔍 → ✅ Spec ok, respektive ⚖️ → 📤 Klar – till kund.
672
- - **`underkänn <nr|gid> <skäl>`:** skäl är obligatoriskt — utan skäl, fråga
673
- efter det. request-sync lägger skälet som kommentar på posten, bumpar
674
- iterationsräknaren och flyttar status bakåt enligt sina bakåtflyttsregler.
675
- - **Post utan bräda** (bara arkiv): `godkänn` antecknar
676
- `approval: {source: "manual", date}` i `state.json` efter en uttrycklig
677
- bekräftelsefråga. Säg tydligt att detta inte syns för någon annan än arkivet.
678
- - Verben rör Offer status och kommentarer — aldrig Score, Class eller
679
- beskrivningsfält.
680
- - **Avsluta enligt `## Avslutning: var posten står`** när posten har en
681
- arkivkatalog: tabellen ur `archive.mjs nasta-steg` plus brädraden, så
682
- flytten syns tillsammans med vad den öppnar för (`bedöm`, kund, inget).
683
-
684
- ## Uppgift: `döm`
685
-
686
- Människans dom över en post vars dubbelkörning skrev OLIKA. Adressering som
687
- `godkänn` (radnummer eller gid, aldrig fuzzy). Kräver att spärren står
688
- (`archive.mjs sparr` exit 1), annars: visa status och stanna.
689
-
690
- **En variantkatalog (från `utvärdera`) adresseras med sin sökväg direkt,
691
- aldrig med radnummer eller gid.** Experimentets slug är samma sträng som ett
692
- vanligt gid skulle vara, så gid-adressering skulle resolva till postkatalogen
693
- på toppnivå, inte till variantkatalogen under `experiments/<slug>/`.
694
- `archive.mjs` tar ändå en katalog som argument oavsett var den ligger, så
695
- sökvägen fungerar identiskt.
696
-
697
- **En dom på en katalog under `experiments/` skriver aldrig om
698
- `experiment.json` i sig.** `archive.mjs utslag` och `archive.mjs sparr`
699
- verkar bara på variantkatalogens egen `state.json`. Statusen i
700
- `experiment.json` uppdateras inte av `döm` — den uppdateras av `utvärdera`,
701
- körd igen för samma post, vars återupptagbarhetsregel omvärderar en
702
- `"väntar på dom"`-variant och gör utfallet synligt för rapporten när den körs
703
- (se `utvärdera`).
704
-
705
- 1. **Visa underlaget.** Kör jämförelsescriptet på det gällande paret och visa
706
- varje diffrad med beläggen sida vid sida (som i bedöm steg 6). Ingen
707
- sammanfattning i stället för beläggen: domen fattas på dem.
708
- 2. **Fråga granskaren** (AskUserQuestion) med tre vägar.
709
- - **"Körning N gäller"** (N är 1 eller 2, vilken av de två uppmätta
710
- 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>`.
711
- (`--axel classification` finns kvar enbart för domar i äldre arkiv
712
- med en klassaxel, före ADR-0098.)
713
- `--n` är INTE körningsnumret i rubriken ovan: det är det AKTUELLA
714
- varvet, avläst ur den senaste pricing-filen
715
- `score-pricing-v<n>.json` (aldrig `next-n`s utdata, som pekar på
716
- NÄSTA, ännu okörda varv). `--galler-fil` är den arkiverade fil
717
- granskaren dömer till
718
- förmån för: körning 1 → `score-pricing-v<n>.json` utan suffix,
719
- körning 2 → `score-pricing-v<n>-run2.json`. Efter en beställd
720
- omkörning (nästa väg) finns TRE arkiverade filer, och
721
- `--galler-fil` kan peka på VILKEN som helst av dem: omkörningens egen
722
- fil (version = gällande `n`), eller endera av de två ursprungliga
723
- körningarnas filer från ett LÄGRE varv, om granskaren dömer att en av
724
- dem hade rätt hela tiden. Scriptet vägrar bara ett HÖGRE varv än `--n`
725
- (en dom kan aldrig gälla ett varv som inte fanns när den skrevs) och en
726
- fil som inte faktiskt arkiverades via `add-run`. Kör `archive.mjs sparr`
727
- efteråt: exit 0 betyder att spärren är lyft, exit 1 betyder att
728
- något fortfarande väntar (utskriften namnger vad).
729
- Inga filer döps om eller skrivs om.
730
- - **"Beställ omkörning":** EN ny körning, ingen tvångsdubbling: dispatcha
731
- scorern (ordinarie promptmall och kontraktskedja), arkivera med
732
- `partial_rerun` och `pairs_with` mot den stående filen. Den nya
733
- raden väntar precis som originalraden på en dom (`archive.mjs sparr`
734
- spärrar den ensam, oavsett vad den råkar bära i `double_run`): visa
735
- scriptdiffen mot BÅDA de stående körningarna och gå till steg 2 igen,
736
- det är fortfarande granskaren som dömer. Vill granskaren ha två nya
737
- mätningar beställs två.
738
- - **"Underlaget är fel":** granskarens beslut appendas i `impact.md` som
739
- citatblock enligt mallen nedan, assessorn körs om (ny `impact.md`),
740
- därefter kör `bedöm` ett nytt fullt varv. En körning mot
741
- gammalt underlag paras aldrig med en mot nytt.
742
- 3. **Kalibreringsnotis.** Är oenigheten äkta (en fråga eller ett ankarval som
743
- 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>"`.
744
- Notisen är råmaterial till nästa instruktionsversion (mekanismen bakom
745
- v0.2 till v0.6) och läses aldrig in i någon prompt. `archive.mjs kalibrering <arkivrot>`
746
- listar alla.
747
- 4. **Avsluta enligt `## Avslutning: var posten står`**: tabellen ur
748
- `archive.mjs nasta-steg` visar om spärren lyfts (raden pekar då vidare på
749
- sync/godkänn) eller fortfarande väntar (`döm` igen efter en beställd
750
- omkörning), plus brädraden.
751
-
752
- **Granskarutslag skrivs som citat, aldrig som prosa.** När granskarens
753
- beslut ska in i underlaget, alltså när det ändrar eller preciserar en
754
- sakuppgift i analysen, eller av annat skäl måste stå i underlaget för
755
- spårbarhet: appendas det sist i `impact.md` som ett avgränsat block enligt
756
- mallen nedan, beslutet ordagrant citerat, aldrig omformulerat av
757
- dispatchern och aldrig invävt i analysens löptext. En fritt formulerad
758
- utslagsmening läses av nästa körning som sakuppgift och styr om
759
- bedömningen, det var precis felet i pilotkörningarna, därför alltid mallen,
760
- aldrig fri prosa. Kräver beslutet en faktisk sakändring i analysen: kör om
761
- assessorn (ny `impact.md`). Underlaget har i båda fallen ändrats, så
762
- `bedöm` kör ett nytt fullt varv (se steg 2 ovan, vägen "Underlaget är fel").
763
- Skriv samtidigt kvittot `archive.mjs utslag <katalog> --typ sakandring --beslut "<ordagrant>" --beslutad-av <namn>`
764
- i `state.json` (se `granskarutslag` ovan): citatet i `impact.md` är
765
- underlaget, sakandring-raden är bara revisionsspåret. Ett rent
766
- viktningsutslag, där granskaren bara konstaterar att en körning hade rätt
767
- utan att röra någon sakuppgift, hör ALDRIG hemma här: det skrivs i stället
768
- via `archive.mjs utslag --typ viktning` (se steg 2 ovan, vägen "Körning N
769
- gäller"), just för att underlaget ska förbli oförändrat. Blocket är internt
770
- underlag med samma status som Till granskaren-blocket och följer aldrig
771
- med i kundtext.
684
+ ## Task: `status`
685
+
686
+ Run request-sync's queue reading in the main conversation for the states
687
+ 📥 Inkommet, 🔍 Spec redo för granskning, ✅ Spec ok and ⚖️ Poängsättning redo för granskning.
688
+ Reply as a BLUF table with **row numbers**: `nr · item · Offer status`.
689
+ Save the nr → gid mapping in the session (the gid need not be shown in
690
+ the table): the numbers are what `godkänn`/`underkänn`/`bedöm`/`detaljer` refer
691
+ to, and every verb looks up and acts on the **gid**, never on the number itself.
692
+ No JSON in the chat.
693
+
694
+ ## Tasks: `godkänn` / `underkänn`
695
+
696
+ Shortcuts to the same lever as in Asana: **Offer status on the board remains the only
697
+ decision**. The verbs make the decision faster to carry out, never easier to
698
+ get wrong:
699
+
700
+ - **Addressing:** a row number from the latest `status` listing in the same session
701
+ (looked up to a gid via the saved mapping), or a gid directly. NEVER
702
+ fuzzy heading matching: the wrong item approved is worse than a stop. If there is no
703
+ fresh listing and no gid: run `status` first.
704
+ - **One item per verb call.** `godkänn 2 och 4` is two calls in a row, each
705
+ reading and moving its own item. Never batch approval on "everything looks fine".
706
+ - **State gate for both verbs:** read the item's current Offer status via
707
+ request-sync first. The verbs act only from the review states
708
+ (🔍 Spec redo för granskning, ⚖️ Poängsättning redo för granskning). If
709
+ the item stands anywhere else: show the status and stop instead of guessing.
710
+ - **`godkänn <nr|gid>`:** 🔍 → ✅ Spec ok, and ⚖️ → 📤 Klar – till kund respectively.
711
+ - **`underkänn <nr|gid> <reason>`:** the reason is mandatory: without a reason, ask
712
+ for it. request-sync adds the reason as a comment on the item, raises
713
+ the iteration counter and moves the status backward per its backward-move rules.
714
+ - **An item without a board** (archive only): `godkänn` notes
715
+ `approval: {source: "manual", date}` in `state.json` after an explicit
716
+ confirmation question. Say clearly that this is not visible to anyone other than the archive.
717
+ - The verbs touch Offer status and comments, never Score, Class or
718
+ description fields.
719
+ - **Close per `## Closing: where the item stands`** when the item has an
720
+ archive directory: the table from `archive.mjs nasta-steg` plus the board line, so
721
+ the move is visible together with what it opens up for (`bedöm`, customer, nothing).
722
+
723
+ ## Task: `döm`
724
+
725
+ The human's ruling on an item whose double run wrote OLIKA. Addressing as for
726
+ `godkänn` (row number or gid, never fuzzy). Requires the hold to stand
727
+ (`archive.mjs sparr` exit 1), otherwise: show the status and stop.
728
+
729
+ **A variant directory (from `utvärdera`) is addressed with its path directly,
730
+ never with a row number or gid.** The experiment's slug is the same string that an
731
+ ordinary gid would be, so gid addressing would resolve to the item directory
732
+ at the top level, not to the variant directory under `experiments/<slug>/`.
733
+ `archive.mjs` takes a directory as its argument regardless of where it lies, so
734
+ the path works identically.
735
+
736
+ **A ruling on a directory under `experiments/` never rewrites
737
+ `experiment.json` itself.** `archive.mjs utslag` and `archive.mjs sparr`
738
+ act only on the variant directory's own `state.json`. The status in
739
+ `experiment.json` is not updated by `döm`: it is updated by `utvärdera`,
740
+ run again for the same item, whose resumability rule re-evaluates a
741
+ `"väntar på dom"` variant and makes the outcome visible to the report when it runs
742
+ (see `utvärdera`).
743
+
744
+ 1. **Show the source material.** Run the comparison script on the current pair and show
745
+ every diff row with the evidence side by side (as in bedöm step 6). No
746
+ summary instead of the evidence: the ruling is made on it.
747
+ 2. **Ask the reviewer** (AskUserQuestion) with three paths.
748
+ - **"Run N holds"** (N is 1 or 2, whichever of the two measured
749
+ runs was right): write the ruling via `archive.mjs utslag <katalog> --typ viktning --beslut "<ordagrant>" --beslutad-av <namn> --axel score --n <n> --galler-fil <fil>`.
750
+ (`--axel classification` remains solely for rulings in older archives
751
+ with a class axis, before ADR-0098.)
752
+ `--n` is NOT the run number in the heading above: it is the CURRENT
753
+ round, read from the latest pricing file
754
+ `score-pricing-v<n>.json` (never `next-n`'s output, which points to the
755
+ NEXT, not yet run round). `--galler-fil` is the archived file
756
+ the reviewer rules in
757
+ favor of: run 1 → `score-pricing-v<n>.json` without a suffix,
758
+ run 2 → `score-pricing-v<n>-run2.json`. After an ordered
759
+ rerun (the next path) there are THREE archived files, and
760
+ `--galler-fil` can point to ANY of them: the rerun's own
761
+ file (version = the current `n`), or either of the two original
762
+ runs' files from a LOWER round, if the reviewer rules that one of
763
+ them was right all along. The script refuses only a HIGHER round than `--n`
764
+ (a ruling can never apply to a round that did not exist when it was written) and a
765
+ file that was not actually archived via `add-run`. Run `archive.mjs sparr`
766
+ afterwards: exit 0 means that the hold is lifted, exit 1 means that
767
+ something is still waiting (the output names what).
768
+ No files are renamed or rewritten.
769
+ - **"Order a rerun":** ONE new run, no forced doubling: dispatch
770
+ the scorer (the ordinary prompt template and contract chain), archive with
771
+ `partial_rerun` and `pairs_with` against the standing file. The new
772
+ row waits, just like the original row, for a ruling (`archive.mjs sparr`
773
+ holds it on its own, regardless of what it happens to carry in `double_run`): show
774
+ the script diff against BOTH standing runs and go to step 2 again,
775
+ it is still the reviewer who rules. If the reviewer wants two new
776
+ measurements, two are ordered.
777
+ - **"The source material is wrong":** the reviewer's decision is appended to `impact.md` as a
778
+ quote block per the template below, the assessor is rerun (a new `impact.md`),
779
+ after which `bedöm` runs a new full round. A run against
780
+ old source material is never paired with one against new.
781
+ 3. **Calibration notice.** If the disagreement is genuine (a question or an anchor choice that
782
+ two valid runs read differently, not a label): write `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>"`.
783
+ The notice is raw material for the next instruction version (the mechanism behind
784
+ v0.2 to v0.6) and is never read into any prompt. `archive.mjs kalibrering <arkivrot>`
785
+ lists all of them.
786
+ 4. **Close per `## Closing: where the item stands`**: the table from
787
+ `archive.mjs nasta-steg` shows whether the hold has been lifted (the row then points on to
788
+ sync/godkänn) or is still waiting (`döm` again after an ordered
789
+ rerun), plus the board line.
790
+
791
+ **Reviewer rulings are written as quotes, never as prose.** When the reviewer's
792
+ decision must go into the source material, that is, when it changes or refines a
793
+ fact in the analysis, or for another reason must stand in the source material for
794
+ traceability: it is appended last in `impact.md` as a delimited block per
795
+ the template below, the decision quoted verbatim, never reworded by
796
+ the dispatcher and never woven into the analysis's running text. A freely worded
797
+ ruling sentence is read by the next run as a fact and steers
798
+ the assessment, which was exactly the error in the pilot runs, therefore always the template,
799
+ never free prose. If the decision requires an actual factual change in the analysis: rerun
800
+ the assessor (a new `impact.md`). The source material has changed in both cases, so
801
+ `bedöm` runs a new full round (see step 2 above, the path "The source material is wrong").
802
+ At the same time, write the receipt `archive.mjs utslag <katalog> --typ sakandring --beslut "<ordagrant>" --beslutad-av <namn>`
803
+ in `state.json` (see `granskarutslag` above): the quote in `impact.md` is
804
+ the source material, the sakandring row is only the audit trail. A pure
805
+ weighting ruling, where the reviewer only states that a run was right
806
+ without touching any fact, NEVER belongs here: it is written instead
807
+ via `archive.mjs utslag --typ viktning` (see step 2 above, the path "Run N
808
+ holds"), precisely so that the source material stays unchanged. The block is internal
809
+ source material with the same status as the For the reviewer block and never goes
810
+ along in customer text.
772
811
 
773
812
  ```
774
- ## Granskarutslag (varv <N>, <YYYY-MM-DD>)
813
+ ## Reviewer ruling (round <N>, <YYYY-MM-DD>)
775
814
 
776
- > <granskarens beslut, ordagrant>
815
+ > <the reviewer's decision, verbatim>
777
816
 
778
- Utslaget gäller endast viktningen mellan körningarna och ändrar inga
779
- sakuppgifter i analysen ovan.
817
+ The ruling applies only to the weighting between the runs and changes no
818
+ facts in the analysis above.
780
819
  ```
781
820
 
782
- ## Uppgift: `detaljer`
783
-
784
- `detaljer <gid|slug|nr> [steg]` — skriv ut arkiverade filer för posten: utan
785
- steg en lista över vad som finns, med steg (`spec`, `granskarblock`, `analys`,
786
- `poäng`, `state`) filens innehåll rått; `klass` läser äldre arkivs
787
- classification-filer (före ADR-0098). För `klass`/`poäng`: senaste
788
- `-pricing-vN.json` utan suffix (aldrig `-run2`/`-avvisad`).
789
-
790
- ## Uppgift: `utvärdera`
791
-
792
- Modellutvärdering: `utvärdera <post…> <modell…>`, med minst en modell. Kör en
793
- post genom hela flödet en gång per namngiven modell, varje varv med samtliga
794
- tre agenter (specifiern, assessorn, scorern) tvingade till varvets
795
- modell, så varje varv mäter en faktisk modellskillnad i stället för brus mellan
796
- en fast produktionsmix och en enda experimentmodell.
797
- `default` som modellvärde ger agentdefaults (dagens produktionsblandning) och
798
- kör utan override; katalognamnet blir då `baseline/`, för alla andra värden
799
- `<modell>/`. Arkivera all output sida vid sida under experimentytan.
800
- Uppgiften samlar underlag; människan analyserar. Ingen dom fälls över vilken
801
- modell som är bäst, och ingen dom simuleras: en OLIKA i något varvs
802
- dubbelkörning väntar på `döm`, precis som i produktion.
803
-
804
- **Underkommandon resolvas först.** Är det enda argumentet `sammanställ` eller
805
- `rapport`, är det underkommandot `utvärdera sammanställ` respektive
806
- `utvärdera rapport` (se avsnitten nedan). Argumentreglerna nedan gäller aldrig
807
- de orden; de klassificeras varken som modell eller post-id.
808
-
809
- **Flaggorna plockas bort först.** `--om` och `--hoppa-över` lyfts ur
810
- argumentlistan INNAN skanningen nedan börjar, och räknas varken som modell
811
- eller post-id. Utan det steget stoppar en flagga skanningen, noll modeller
812
- hittas, och kommandot faller på "Minst en modell krävs" trots att modellen
813
- står där.
814
-
815
- **Argumenttolkning.** Kvarvarande avslutande argument avgörs höger till vänster
816
- för att hitta GRÄNSEN mot post-id-listan: ett argument räknas som modell bara
817
- om det är ett känt agentmodellnamn (`fable`, `opus`, `sonnet`, `haiku`, …) eller
818
- literalen `default`, allt annat läses som post-id, och skanningen stannar vid
819
- det första argumentet som inte är ett modellnamn. Ett argument som varken är
820
- ett känt modellnamn eller ett giltigt gid/slug är ett FEL, aldrig en gissning:
821
- visa felet och stanna i stället för att chansa på vilken sida av gränsen det
822
- hör hemma. De modellargument skanningen hittar bildar sedan modellistan i
823
- DEKLARATIONSORDNING, vänster till höger.
824
-
825
- **Minst en modell krävs.** Hittar skanningen ingen modell alls är det ett FEL,
826
- aldrig en default. Ett kommando som kostar timmar och pengar ska säga vilka
827
- modeller det kör; en tyst standarduppsättning gör att en körning kan starta med
828
- en annan modell än den som avsågs, och den kostnaden syns först efteråt.
829
-
830
- **Dubbletter vägras.** Står samma modellvärde två gånger i listan, vägra
831
- körningen med ett felmeddelande som förklarar varför: två lika värden pekar på
832
- samma katalognamn och mäter ingen skillnad mot varandra. Vill du köra samma
833
- modell en gång till, gör det i en egen invokation, då blir det en ny körning
834
- under samma modell enligt regeln i Experimentytan.
835
-
836
- **En namngiven modell rör bara sin egen katalog.** Övriga modellers kataloger
837
- läses inte och skrivs inte. Det är det som gör att en jämförelse kan byggas i
838
- etapper: `utvärdera <gid> opus sonnet` idag och `utvärdera <gid> fable` en
839
- vecka senare ger tre modeller under samma post, utan att de första två körs om.
840
-
841
- **Omkörning frågas fram, den antas aldrig.** Namnger invokationen en modell som
842
- redan har körningar under sig, stanna INNAN något dyrt startas och fråga om de
843
- ska köras om. Ställ EN fråga som täcker alla kolliderande modeller, inte en per
844
- modell. Svaret kan ges i förväg med `--om` (kör om) eller `--hoppa-över` (rör
845
- dem inte); bär invokationen någon av dem ställs ingen fråga.
846
-
847
- En omkörning skriver aldrig över den gamla körningen, den blir `k<N+1>`.
848
- Frågan gäller alltså kostnaden, inte risken att förlora data.
849
-
850
- 1. **Underlag.** Som `speca` steg 1: rått önskemål eller gid — vid gid hämtas
851
- posten läsande via request-sync i huvudkonversationen, uppgiftens ENDA
852
- Asana-kontakt. Skapa `experiments/<slug>/` och skriv underlaget till disk
853
- med fasta namn: önskemålet ordagrant som `experiments/<slug>/onskemal.md`
854
- och källmanifestet (steg 2) som `experiments/<slug>/kallmanifest.md`.
855
- Namnen är fasta därför att `korning.mjs` (steg 4) läser dem därifrån och
856
- bygger varje agentprompt av dem; ett underlag som bara finns i
857
- konversationen når aldrig en headless-process.
858
-
859
- Skapa sedan `experiment.json` med ticketreferens, datum och `varianter` i
860
- deklarationsordning, alltid genom kommandot:
821
+ ## Task: `detaljer`
822
+
823
+ `detaljer <gid|slug|nr> [step]`: print archived files for the item: without
824
+ a step, a list of what exists; with a step (`spec`, `granskarblock`, `analys`,
825
+ `poäng`, `state`) the file's content raw; `klass` reads older archives'
826
+ classification files (before ADR-0098). For `klass`/`poäng`: the latest
827
+ `-pricing-vN.json` without a suffix (never `-run2`/`-avvisad`).
828
+
829
+ ## Task: `utvärdera`
830
+
831
+ Model evaluation: `utvärdera <item…> <model…>`, with at least one model. Runs an
832
+ item through the whole flow once per named model, each round with all
833
+ three agents (the specifier, the assessor, the scorer) forced to the round's
834
+ model, so that each round measures an actual model difference instead of noise between
835
+ a fixed production mix and a single experiment model.
836
+ `default` as the model value gives the agent defaults (today's production mix) and
837
+ runs without an override; the directory name then becomes `baseline/`, for all other values
838
+ `<modell>/`. Archive all output side by side under the experiment area.
839
+ The task gathers material; the human analyzes. No ruling is made on which
840
+ model is best, and no ruling is simulated: an OLIKA in any round's
841
+ double run waits for `döm`, just as in production.
842
+
843
+ **Subcommands are resolved first.** If the only argument is `sammanställ` or
844
+ `rapport`, it is the subcommand `utvärdera sammanställ` or
845
+ `utvärdera rapport` respectively (see the sections below). The argument rules below never apply to
846
+ those words; they are classified neither as a model nor as an item id.
847
+
848
+ **The flags are removed first.** `--om` and `--hoppa-över` are lifted out of
849
+ the argument list BEFORE the scan
850
+ below begins, and count neither as a model nor as an item id. Without that step a flag stops the scan, zero models
851
+ are found, and the command fails on "At least one model is required" even though the model
852
+ is there.
853
+
854
+ **Argument parsing.** The remaining trailing arguments are decided right to left
855
+ to find the BOUNDARY with the item id list: an argument counts as a model only
856
+ if it is a known agent model name (`fable`, `opus`, `sonnet`, `haiku`, …) or
857
+ the literal `default`, everything else is read as an item id, and the scan stops at
858
+ the first argument that is not a model name. An argument that is neither
859
+ a known model name nor a valid gid/slug is an ERROR, never a guess:
860
+ show the error and stop instead of gambling on which side of the boundary it
861
+ belongs. The model arguments the scan finds then form the model list in
862
+ DECLARATION ORDER, left to right.
863
+
864
+ **At least one model is required.** If the scan finds no model at all, it is an ERROR,
865
+ never a default. A command that costs wall-clock time and money must say which
866
+ models it runs; a silent default set means that a run can start with
867
+ a different model than the one intended, and that cost shows only afterwards.
868
+
869
+ **Duplicates are refused.** If the same model value stands twice in the list, refuse
870
+ the run with an error message that explains why: two equal values point to
871
+ the same directory name and measure no difference against each other. If you want to run the same
872
+ model once more, do it in a separate invocation; then it becomes a new run
873
+ under the same model per the rule in The experiment area.
874
+
875
+ **A named model touches only its own directory.** Other models' directories
876
+ are neither read nor written. That is what allows a comparison to be built in
877
+ stages: `utvärdera <gid> opus sonnet` today and `utvärdera <gid> fable` a
878
+ week later give three models under the same item, without the first two being rerun.
879
+
880
+ **A rerun is asked for, never assumed.** If the invocation names a model that
881
+ already has runs under it, stop BEFORE anything expensive starts and ask whether they
882
+ are to be rerun. Ask ONE question that covers all colliding models, not one per
883
+ model. The answer can be given in advance with `--om` (rerun) or `--hoppa-över` (do not touch
884
+ them); if the invocation carries either of them, no question is asked.
885
+
886
+ A rerun never overwrites the old run, it becomes `k<N+1>`.
887
+ The question is therefore about the cost, not the risk of losing data.
888
+
889
+ 1. **Source material.** As in `speca` step 1: a raw request or a gid; with a gid the
890
+ item is fetched read-only via request-sync in the main conversation, the task's ONLY
891
+ Asana contact. Create `experiments/<slug>/` and write the source material to disk
892
+ with fixed names: the request verbatim as `experiments/<slug>/onskemal.md`
893
+ and the source manifest (step 2) as `experiments/<slug>/kallmanifest.md`.
894
+ The names are fixed because `korning.mjs` (step 4) reads them from there and
895
+ builds every agent prompt from them; source material that exists only in
896
+ the conversation never reaches a headless process.
897
+
898
+ Then create `experiment.json` with the ticket reference, date and `varianter` in
899
+ declaration order, always through the command:
861
900
 
862
901
  ```
863
902
  node <skillkatalog>/scripts/archive.mjs experiment experiments/<slug> \
@@ -867,59 +906,59 @@ Frågan gäller alltså kostnaden, inte risken att förlora data.
867
906
  --variant <m>...
868
907
  ```
869
908
 
870
- Nyckelnamnen är rapportens: `report.mjs` läser `request_id`, `title`,
871
- `permalink_url` och `datum`, och `svep.mjs` läser `request_id` (med
872
- `ticket` som reserv i äldre filer) till varje varvs REQUEST_ID. Äldre
873
- filer med `ticket`/`titel` läses som förut men skrivs inte längre.
874
- `manifest_sha256` (steg 2) och `eskalerad`/`eskaleringsorsak` (steg 3)
875
- sätts när de är kända, i egna anrop av samma kommando; ett fält som redan
876
- bär samma värde är no-op. `sprak` avläses ur önskemålets brödtext av
877
- huvudkonversationen, precis som i `speca` steg 2; `korning.mjs` vägrar
878
- starta utan den (den gissar aldrig språk, ADR-0114).
879
-
880
- Anteckna samtidigt postens `permalink_url` (`--satt permalink_url=<url>`)
881
- när gid-svaret bär det. Det kommer ur samma läsande hämtning
882
- som redan görs, så steget får ingen ny Asana-kontakt. Saknas fältet i
883
- svaret skrivs det inte, och rapporten visar då gid som ren text: Asanas
884
- permalänk bär ett workspace-gid som inte finns på experimentytan, så en
885
- konstruerad URL vore en gissning.
886
-
887
- **`archive.mjs experiment` är den ENDA skrivaren av `experiment.json`.**
888
- Ingen process, varken huvudkonversationen, startaren eller den inre
889
- processen, skriver filen för hand. Kommandot skapar filen om den saknas
890
- och läser annars in den först: `varianter` slås ihop som UNION med bevarad
891
- deklarationsordning (befintliga namn först, invokationens nya efter), en
892
- befintlig körnings `status` skrivs aldrig över av en process som inte äger just den
893
- körningen, och skrivningen är ATOMISK under ett lås i experimentkatalogen. Skälet till att det är ett kommando: `experiment.json`
894
- är den enda filen på experimentytan som delas mellan samtidiga processer,
895
- och låset är det som gör parallella körningar (steg 4) säkra. Utan det
896
- tappar två samtidiga processer varandras rader, vilket är exakt vad som
897
- hände sonnet/k4 i IPC 14001 (#493).
898
-
899
- 2. **Källmanifest EN gång.** Bygg manifestet som i `speca` steg 2, skriv det
900
- till `experiments/<slug>/kallmanifest.md` (steg 1) och
901
- återanvänd det byteidentiskt i ALLA varv. Anteckna manifest-hash med
909
+ The key names are the report's: `report.mjs` reads `request_id`, `title`,
910
+ `permalink_url` and `datum`, and `svep.mjs` reads `request_id` (with
911
+ `ticket` as a fallback in older files) into every round's REQUEST_ID. Older
912
+ files with `ticket`/`titel` are read as before but no longer written.
913
+ `manifest_sha256` (step 2) and `eskalerad`/`eskaleringsorsak` (step 3)
914
+ are set when they are known, in separate calls of the same command; a field that already
915
+ carries the same value is a no-op. `sprak` is read from the request's body text by
916
+ the main conversation, just as in `speca` step 2; `korning.mjs` refuses
917
+ to start without it (it never guesses the language, ADR-0114).
918
+
919
+ At the same time, note the item's `permalink_url` (`--satt permalink_url=<url>`)
920
+ when the gid reply carries it. It comes from the same read-only fetch
921
+ that is already made, so the step gets no new Asana contact. If the field is missing from
922
+ the reply, it is not written, and the report then shows the gid as plain text: Asana's
923
+ permalink carries a workspace gid that does not exist in the experiment area, so a
924
+ constructed URL would be a guess.
925
+
926
+ **`archive.mjs experiment` is the ONLY writer of `experiment.json`.**
927
+ No process, neither the main conversation, the starter nor the inner
928
+ process, writes the file by hand. The command creates the file if it is missing
929
+ and otherwise reads it in first: `varianter` is merged as a UNION with preserved
930
+ declaration order (existing names first, the invocation's new ones after), an
931
+ existing run's `status` is never overwritten by a process that does not own that very
932
+ run, and the write is ATOMIC under a lock in the experiment directory. The reason it is a command: `experiment.json`
933
+ is the only file in the experiment area that is shared between
934
+ concurrent processes, and the lock is what makes parallel runs (step 4) safe. Without it,
935
+ two concurrent processes lose each other's rows, which is exactly what
936
+ happened to sonnet/k4 in IPC 14001 (#493).
937
+
938
+ 2. **Source manifest ONCE.** Build the manifest as in `speca` step 2, write it
939
+ to `experiments/<slug>/kallmanifest.md` (step 1) and
940
+ reuse it byte-identically in ALL rounds. Note the manifest hash with
902
941
  `archive.mjs experiment experiments/<slug> --satt manifest_sha256=<hash>`
903
- (`ref-hash.mjs` på manifesttexten). Enda avsedda
904
- skillnaden mellan varven är modellen.
905
- Manifestet har sist sektionen `## Avgjorda val`: varje val som en människa
906
- avgjort efter ett tidigare stopp, en rad var, ordagrant som svaret gavs;
907
- `inga` om inga. Specifiern får aldrig flagga ett val som står där.
908
- 3. **Eskaleringsvalet görs en gång** enligt `speca`:s regler och gäller alla
909
- varv, även eskaleringsvalet är en del av det som hålls konstant.
910
- Triggar valet: agenten blir `request-specifier-eskalerad` i stället för
911
- `request-specifier`, likadant i alla varv. Modell-overriden i steg 4
912
- gäller ändå den eskalerade agenten precis som de två andra: dess egen
913
- pinnade modell, den som enligt agentdefinitionen ÄR eskaleringen, skrivs
914
- över av varvets modell. Eskaleringsmekanismen kollapsar alltså medvetet
915
- under override, det är avsett, inte en lucka: `utvärdera` mäter en ren
916
- modellskillnad över alla tre rollerna och testar aldrig
917
- eskaleringsbeslutet i sig. Valet antecknas med
942
+ (`ref-hash.mjs` on the manifest text). The only intended
943
+ difference between the rounds is the model.
944
+ The manifest ends with the section `## Avgjorda val`: every choice that a human
945
+ has decided after an earlier stop, one line each, verbatim as the answer was given;
946
+ `inga` if there are none. The specifier must never flag a choice that stands there.
947
+ 3. **The escalation choice is made once** per `speca`'s rules and applies to all
948
+ rounds; the escalation choice too is part of what is held constant.
949
+ If the choice triggers: the agent becomes `request-specifier-eskalerad` instead of
950
+ `request-specifier`, the same in all rounds. The model override in step 4
951
+ still applies to the escalated agent just as to the other two: its own
952
+ pinned model, the one that per the agent definition IS the escalation, is
953
+ overwritten by the round's model. The escalation mechanism thus deliberately collapses
954
+ under override; that is intended, not a gap: `utvärdera` measures a pure
955
+ model difference across all three roles and never tests
956
+ the escalation decision itself. The choice is noted with
918
957
  `archive.mjs experiment experiments/<slug> --satt eskalerad=<true|false> --satt eskaleringsorsak=<text>`;
919
- det är därifrån `svep.mjs` läser `ESKALERAD:` till varje varvs prompt.
920
- 4. **Ett varv per modell i listan**, startat av `svep.mjs`. Huvudkonversationen
921
- förbereder steg 1 till 3 och kör sedan, även för en enda post och en enda
922
- modell:
958
+ it is from there that `svep.mjs` reads `ESKALERAD:` into every round's prompt.
959
+ 4. **One round per model in the list**, started by `svep.mjs`. The main conversation
960
+ prepares steps 1 to 3 and then runs, even for a single item and a single
961
+ model:
923
962
 
924
963
  ```
925
964
  node <skillkatalog>/scripts/svep.mjs --repo-rot <abs> --konto <namn> \
@@ -928,504 +967,504 @@ Frågan gäller alltså kostnaden, inte risken att förlora data.
928
967
  [--tickets-parallellt 2] [--modeller-parallellt 4] [--om | --hoppa-över]
929
968
  ```
930
969
 
931
- Varje varv startas av `svep.mjs` som EN `korning.mjs`-process, och det är
932
- det som ger mätningen gratis. Parallellregeln: två tickets åt gången, högst
933
- fyra modeller för en ticket i flykt (`--modeller-parallellt`, #526), och
934
- nästa ticket startar först när en tickets samtliga
935
- modeller är klara. Det är säkert därför att körningskatalogerna redan är
936
- isolerade (`ny-korning` är atomisk, varje varv har egen katalog, manifest
937
- och repo läses bara) och det enda delade tillståndet, `experiment.json`,
938
- skrivs under lås (steg 1). Ansvaret delas, eftersom en process aldrig kan känna sin egen
939
- slutkostnad:
940
-
941
- **Startaren** är `svep.mjs`. Dess ansvar per varv, i en mening: allokera
942
- körningskatalogen med
970
+ Every round is started by `svep.mjs` as ONE `korning.mjs` process, and that is
971
+ what gives the measurement for free. The parallel rule: two tickets at a time, at most
972
+ four models for one ticket in flight (`--modeller-parallellt`, #526), and
973
+ the next ticket starts only when all of a ticket's
974
+ models are done. That is safe because the run directories are already
975
+ isolated (`ny-korning` is atomic, every round has its own directory, manifest
976
+ and repo are only read) and the only shared state, `experiment.json`,
977
+ is written under a lock (step 1). The responsibility is split, since a process can never know its own
978
+ final cost:
979
+
980
+ **The starter** is `svep.mjs`. Its responsibility per round, in one sentence: allocate
981
+ the run directory with
943
982
  `archive.mjs ny-korning <modellkatalog> --request-id <gid eller slug>`
944
- (kommandot skriver också skelettet till körningens `state.json`, se
945
- Experimentytan; den inre processen skriver aldrig den filen från noll),
946
- starta `korning.mjs` för varvet med körningskatalogen, experimentkatalogen,
947
- repo-roten, request-id:t och modellen som argument (stderr till
948
- `stderr.log`; `korning.json` skriver `korning.mjs` själv),
949
- ta tiden runt processen och skriva
983
+ (the command also writes the skeleton to the run's `state.json`, see
984
+ The experiment area; the inner process never writes that file from scratch),
985
+ start `korning.mjs` for the round with the run directory, the experiment directory,
986
+ the repo root, the request id and the model as arguments (stderr to
987
+ `stderr.log`; `korning.json` is written by `korning.mjs` itself),
988
+ time the process and write
950
989
  `<modell>/k<N>/matning.json` (`startad`, `avslutad`, `wall_ms`,
951
- `exit_code`; skrivs ÄVEN när processen faller), stämpla `proveniens` i
952
- körningens `state.json` med
990
+ `exit_code`; written EVEN when the process fails), stamp `proveniens` in
991
+ the run's `state.json` with
953
992
  `archive.mjs proveniens <modell>/k<N> --modell <varvets modellvärde> --konto <Claude-kontot som körde> --underlag <sökväg till kallmanifest.md>`
954
- EFTER att `korning.json` skrivits, verifiera modellen mot `modelUsage`,
955
- och sätta körningens status via `archive.mjs experiment`. Hur vart och ett
956
- görs bor i scriptet och dess tester, inte här. Att stegen står som
957
- kommandon är med flit (ADR-0094): ett steg som ålägger startaren en
958
- skrivning utan att peka ut vem som utför den är en skrivning
959
- som inte blir av, och den syns först som tomma celler långt senare.
960
-
961
- Fyra regler i den kedjan är kontrakt mot rapporten och arkivet och står
962
- därför kvar här med sitt skäl:
963
-
964
- - **Väggtiden bor i `matning.json`, aldrig i `korning.json`.** Rapporten
965
- läser `total_cost_usd`, `usage.input_tokens`, `usage.output_tokens`,
966
- `usage.cache_read_input_tokens`, `usage.cache_creation_input_tokens` och
967
- `subagent_stats.spawned` ur `korning.json`, men väggtiden ur
968
- `matning.json`: en process kan inte känna sin egen klocktid när arbetet
969
- ligger i bakgrundsprocesser, och stegsvarens `duration_ms` räknar inte in väntan på dem.
970
- `korning.json` bär summan över stegen, därav den egna filen. En kraschad körning har en klocktid, ofta det intressantaste
971
- talet i tabellen, därför skrivs `matning.json` även vid krasch. Lasten
972
- väggtiden mättes under står i `korning.json`:s steg som
973
- `samtidiga_processer` (levande agentprocesser på maskinen vid stegets
974
- start, räknat med `pgrep`, maskinvitt och best effort, `null` när
975
- räkningen föll); rapporten visar maxvärdet bredvid väggtiden och märker
976
- en körning utan fältet som mätt under okänd samtidighet (#526).
977
- - **Proveniensen stämplas EFTER `korning.json`, aldrig före**, annars
978
- saknar stämpeln faktureringsdatan. Kommandot räknar själv `skill_sha256`
979
- (över `SKILL.md` plus de fyra agentdefinitionerna) och `underlag_sha256`,
980
- läser `korning.json` och antecknar `fakturerade_modeller` (den
981
- kanoniserade nyckelmängden ur `modelUsage`, suffix som `[1m]` strippade,
982
- ADR-0099), och stämplar även varje `del-N/`. Utan den stämpeln går det
983
- inte att avgöra vad som skilde `k1` från `k2`, och en jämförelse över tid
984
- blir en gissning.
985
- - **Modellverifiering görs av startaren mot faktureringen (ADR-0099),
986
- aldrig mot svansraden ensam.** När processen är klar: jämför
987
- `fakturerade_modeller` mot varvets begärda modell plus de två tolererade
988
- avvikelserna, kundtextfixens `sonnet` och CLI:ns `haiku`-hjälpanrop. En
989
- modellnyckel som varken är varvets begärda modell, kundtextfixens
990
- `sonnet` eller CLI:ns `haiku`-hjälpanrop är en faktisk MODELL-avvikelse →
991
- körningen blir `"ofärdig"`. Startaren tolererar `haiku`-nyckeln i summan;
992
- taket på 200 output-tokens, som skiljer hjälpanropet från en riktig
993
- körning, prövas per steg av `korning.mjs`. Resultatet ligger i
994
- `korning.json` som `modellverifiering` (`ok` plus `avvikelser`, där varje
995
- rad namnger sitt steg), så avvikelsen går att peka ut i det steg den
996
- uppstod. En MODELL-svansrad som säger något annat än vad
997
- `modelUsage` visar är en RAPPORTERINGSAVVIKELSE: den visas i rapporten
998
- och står kvar i `runs[]` (raden bär agentens uppgift i
999
- `model.uppgiven`), men den fäller ALDRIG ensam körningen — modeller är
1000
- opålitliga källor om sin egen identitet, och att kasta en korrekt
1001
- körning på en felskriven svansrad betalar om ett riktigt resultat.
1002
- Modellvärdet `default` kör agenternas egna defaults och får lagligt
1003
- producera flera modellnycklar: ingen mängdjämförelse görs där.
1004
- - **Statusen sätts via `archive.mjs experiment`**, aldrig genom att skriva
1005
- filen: exit ≠ 0 eller en faktisk MODELL-avvikelse ger `"ofärdig"` med
1006
- `--orsak`; har den inre processen redan satt `"stopp"`, `"väntar på dom"`
1007
- eller `"ofärdig"` behålls den; annars `"klar"`.
1008
-
1009
- **`korning.mjs` är den inre processen: ett script utan modellturer.** Det
1010
- bygger prompterna ur `onskemal.md`, `kallmanifest.md` och `experiment.json`,
1011
- startar varje agentsteg som en egen process
993
+ AFTER `korning.json` has been written, verify the model against `modelUsage`,
994
+ and set the run's status via `archive.mjs experiment`. How each of these
995
+ is done lives in the script and its tests, not here. That the steps stand as
996
+ commands is deliberate (ADR-0094): a step that imposes a
997
+ write on the starter without pointing out who performs it is a write
998
+ that never happens, and it shows only as empty cells much later.
999
+
1000
+ Four rules in that chain are contracts against the report and the archive and therefore
1001
+ stay here with their reason:
1002
+
1003
+ - **The wall time lives in `matning.json`, never in `korning.json`.** The report
1004
+ reads `total_cost_usd`, `usage.input_tokens`, `usage.output_tokens`,
1005
+ `usage.cache_read_input_tokens`, `usage.cache_creation_input_tokens` and
1006
+ `subagent_stats.spawned` from `korning.json`, but the wall time from
1007
+ `matning.json`: a process cannot know its own clock time when the work
1008
+ lies in background processes, and the step replies' `duration_ms` does not count the waiting for them.
1009
+ `korning.json` carries the sum over the steps, hence the separate file. A crashed run has a clock time, often the most interesting
1010
+ number in the table, therefore `matning.json` is written even on a crash. The load
1011
+ the wall time was measured under stands in `korning.json`'s steps as
1012
+ `samtidiga_processer` (living agent processes on the machine at the step's
1013
+ start, counted with `pgrep`, machine-wide and best effort, `null` when
1014
+ the count failed); the report shows the maximum next to the wall time and marks
1015
+ a run without the field as measured under unknown concurrency (#526).
1016
+ - **The provenance is stamped AFTER `korning.json`, never before**, otherwise
1017
+ the stamp lacks the billing data. The command itself computes `skill_sha256`
1018
+ (over `SKILL.md` plus the four agent definitions) and `underlag_sha256`,
1019
+ reads `korning.json` and notes `fakturerade_modeller` (the
1020
+ canonicalized key set from `modelUsage`, suffixes like `[1m]` stripped,
1021
+ ADR-0099), and also stamps every `del-N/`. Without that stamp it is
1022
+ not possible to determine what separated `k1` from `k2`, and a comparison over time
1023
+ becomes a guess.
1024
+ - **Model verification is done by the starter against the billing (ADR-0099),
1025
+ never against the tail line alone.** When the process is done: compare
1026
+ `fakturerade_modeller` against the round's requested model plus the two tolerated
1027
+ deviations, the customer-text fix's `sonnet` and the CLI's `haiku` helper calls. A
1028
+ model key that is neither the round's requested model, the customer-text fix's
1029
+ `sonnet` nor the CLI's `haiku` helper call is an actual `MODELL-avvikelse` →
1030
+ the run becomes `"ofärdig"`. The starter tolerates the `haiku` key in the sum;
1031
+ the cap of 200 output tokens, which distinguishes the helper call from a real
1032
+ run, is tested per step by `korning.mjs`. The result lies in
1033
+ `korning.json` as `modellverifiering` (`ok` plus `avvikelser`, where every
1034
+ row names its step), so the deviation can be pointed out in the step where it
1035
+ arose. A MODELL tail line that says something other than what
1036
+ `modelUsage` shows is a REPORTING DEVIATION: it is shown in the report
1037
+ and stays in `runs[]` (the row carries the agent's claim in
1038
+ `model.uppgiven`), but it NEVER alone fails the run: models are
1039
+ unreliable sources about their own identity, and throwing away a correct
1040
+ run over a miswritten tail line pays again for a real result.
1041
+ The model value `default` runs the agents' own defaults and may legitimately
1042
+ produce several model keys: no set comparison is made there.
1043
+ - **The status is set via `archive.mjs experiment`**, never by writing
1044
+ the file: exit ≠ 0 or an actual `MODELL-avvikelse` gives `"ofärdig"` with
1045
+ `--orsak`; if the inner process has already set `"stopp"`, `"väntar på dom"`
1046
+ or `"ofärdig"`, it is kept; otherwise `"klar"`.
1047
+
1048
+ **`korning.mjs` is the inner process: a script without model turns.** It
1049
+ builds the prompts from `onskemal.md`, `kallmanifest.md` and `experiment.json`,
1050
+ starts every agent step as a process of its own
1012
1051
  `claude -p --agent <agent> --output-format json --max-turns <tak> [--model <modell>]`
1013
- med prompten på stdin, sparar råsvaret under `k<N>/steg/`, kör precheck,
1014
- `kontroll.mjs` och compare-scores, arkiverar via `archive.mjs` och skriver
1015
- `korning.json` som summan. Det gör ingen Asana-hämtning och bygger inget
1016
- nytt manifest. Buntens delar körs parallellt (två åt gången) och de två
1017
- scorerkörningarna samtidigt (#500).
1018
-
1019
- `korning.mjs` kör speca-stegen (dispatch, kontraktskontroll,
1020
- arkivering) och därefter bedöm-stegen (analys, poäng,
1021
- ankarlösning, dubbelkörning enligt bedöm) mot den anvisade katalogen, utan
1022
- godkännandegrind, och rör ingenting utanför den. Är modellen `default`
1023
- används ordinarie promptmallar och agenternas defaults, ingen override. Är
1024
- den ett modellnamn får varje agentprocess modell-override till den på ALLA
1025
- tre agenter; overriden är `--model` på processen, aldrig en promptrad, och
1026
- agentdefinitionerna röres aldrig, effort tas ur definitionen
1027
- som vanligt, utom när `korning.mjs` fått `--effort`: då får varje
1028
- agentprocess samma `--effort`-override, stegposten och `runs[]` bär den
1029
- som begärd (`effort_begard`, `add-run --effort`), aldrig som verifierad,
1030
- eftersom faktureringen inte bär någon effort (#525). Overriden verifieras per steg mot `modelUsage` i råsvaret
1031
- (ADR-0099, se modellverifieringen ovan), svansraden arkiveras men avgör
1032
- aldrig ensam. `approval` förblir `null`, `state.json`
1033
- får `"experiment": true` ur skelettet `ny-korning` skrev. `REQUEST_ID`
1034
- och `--expect-id` använder alltid ticketens gid/slug, aldrig katalogens
1035
- namn.
1036
-
1037
- **Bunt i den inre processen:** ger specifiern flera specar läggs de som
1038
- `del-1/ … del-N/` under körningskatalogen med `bundle` satt (buntregeln i
1039
- Experimentytan), aldrig del 1 i roten; varje del får sin egen `spec.md`,
1040
- `granskarblock.md` och `state.json`, och speca steg 6 (precheck,
1041
- omdispatch per del, `stopp` vid kandidat) körs per del. Faller en del på
1042
- kvarstående brist blir körningen `"ofärdig"`; övriga delar körs ändå
1043
- klart så arkivet visar vad som gick att bedöma.
1044
-
1045
- **DELA_UPP körs inte autonomt av `korning.mjs` (v1).** Utfallet arkiveras
1046
- och körningen får status `stopp` med orsak; delningsanalysen saknar
1047
- promptmall i den här skillen, och ett script får inte improvisera en
1052
+ with the prompt on stdin, saves the raw reply under `k<N>/steg/`, runs precheck,
1053
+ `kontroll.mjs` and compare-scores, archives via `archive.mjs` and writes
1054
+ `korning.json` as the sum. It does no Asana fetch and builds no
1055
+ new manifest. The bundle's parts run in parallel (two at a time) and the two
1056
+ scorer runs at the same time (#500).
1057
+
1058
+ `korning.mjs` runs the speca steps (dispatch, contract check,
1059
+ archiving) and then the bedöm steps (analysis, score,
1060
+ anchor resolution, double run per bedöm) against the directory it is given, without
1061
+ the approval gate, and touches nothing outside it. If the model is `default`,
1062
+ the ordinary prompt templates and the agents' defaults are used, no override. If
1063
+ it is a model name, every agent process gets a model override to it on ALL
1064
+ three agents; the override is `--model` on the process, never a prompt line, and
1065
+ the agent definitions are never touched; the effort is taken from the definition
1066
+ as usual, except when `korning.mjs` has been given `--effort`: then every
1067
+ agent process gets the same `--effort` override, the step entry and `runs[]` carry it
1068
+ as requested (`effort_begard`, `add-run --effort`), never as verified,
1069
+ since the billing carries no effort (#525). The override is verified per step against `modelUsage` in the raw reply
1070
+ (ADR-0099, see the model verification above); the tail line is archived but never
1071
+ decides alone. `approval` stays `null`, `state.json`
1072
+ gets `"experiment": true` from the skeleton `ny-korning` wrote. `REQUEST_ID`
1073
+ and `--expect-id` always use the ticket's gid/slug, never the directory's
1074
+ name.
1075
+
1076
+ **A bundle in the inner process:** if the specifier gives several specs, they are placed as
1077
+ `del-1/ … del-N/` under the run directory with `bundle` set (the bundle rule in
1078
+ The experiment area), never part 1 in the root; every part gets its own `spec.md`,
1079
+ `granskarblock.md` and `state.json`, and speca step 6 (precheck,
1080
+ redispatch per part, `stopp` on a candidate) runs per part. If a part fails on a
1081
+ remaining defect, the run becomes `"ofärdig"`; the other parts are still run
1082
+ to completion so the archive shows what could be assessed.
1083
+
1084
+ **DELA_UPP is not run autonomously by `korning.mjs` (v1).** The outcome is archived
1085
+ and the run gets the status `stopp` with a reason; the split analysis lacks
1086
+ a prompt template in this skill, and a script must not improvise one
1048
1087
  (ADR-0113).
1049
1088
 
1050
- Ett fallet agentsteg, kontraktsbrott (kvarstående BROTT efter omdispatch) eller
1051
- en faktisk MODELL-avvikelse (obegärd nyckel i `modelUsage`, aldrig en
1052
- ensam svansradsavvikelse), i vilket steg det än inträffar, sätter
1053
- körningens `status` till `"ofärdig"` via `archive.mjs experiment`
1089
+ A failed agent step, a contract breach (remaining BROTT after redispatch) or
1090
+ an actual `MODELL-avvikelse` (an unrequested key in `modelUsage`, never a
1091
+ lone tail-line deviation), in whichever step it occurs, sets
1092
+ the run's `status` to `"ofärdig"` via `archive.mjs experiment`
1054
1093
  (`experiments/<slug> --korning <modell>/k<N> --status ofärdig --orsak <vad som föll>`),
1055
- aldrig genom att skriva filen.
1056
-
1057
- 5. **DELA_UPP i experimentet.** Ett DELA_UPP-utfall följer bedöms ordinarie
1058
- delningsregler men utan paus för granskare (i v1 kör `korning.mjs` inte
1059
- delningen själv, se steg 4; reglerna här gäller när den körs):
1060
- delkataloger
1061
- `<modell>/k<N>/del-1/ … del-N/` med `split` satt, förälderns `spec.md` +
1062
- `granskarblock.md` kopierade in, ny analys + poäng per del. Delas en
1063
- buntdel blir det `del-N/del-M/` under den delen, aldrig ett nytt
1064
- syskon i roten (`bundle` och `split` sätts aldrig på samma katalog).
1065
- En del utan tak blir `OVER_SKALAN` och delas aldrig vidare. Övriga
1066
- stopputfall (`TILLBAKA_TILL_SPEC`, `KONCEPT`, `DIAGNOS`, `ESKALERA`,
1067
- `INPUT_SAKNAS`) kräver människa eller kund: arkivera utfallet, sätt
1068
- körningens status till `"stopp"` via `archive.mjs experiment`, avbryt det
1069
- varvet, gör det synligt i rapporten när den körs, och övriga körningar i
1070
- listan fortsätter oberoende.
1071
-
1072
- **OLIKA väntar på dom.** Skriver dubbelkörningen OLIKA i ett varv:
1073
- arkivera enligt bedöms regler (spärren står), sätt körningens status till
1074
- `"väntar på dom"` via `archive.mjs experiment`, synligt i rapporten när
1075
- den körs.
1076
- Domen är människans även i experimentet: `döm` körs mot körningskatalogen
1077
- när granskaren vill (adresserad via sin sökväg, se `döm`), och ingen dom
1078
- simuleras av uppgiften. Domen i sig skriver inte om `experiment.json`,
1079
- det gör `svep.mjs` (via `archive.mjs experiment`) nästa gång `utvärdera`
1080
- körs för samma post (se
1081
- Återupptagbart i avsnittet Batch nedan för hur och när statusen går
1082
- till `"klar"` och rapporten kan visa utfallet). Övriga körningar i listan
1083
- fortsätter oberoende oavsett när, eller om, domen fälls.
1084
-
1085
- 6. **Rapport.** BLUF i chatten: klart eller ofärdigt per körning, stopputfall,
1086
- och sökvägen till `experiments/<slug>/`. Tabellen per körning kommer ur
1087
- `node <skillkatalog>/scripts/archive.mjs bluf <modell>/k<N>` (rekursivt
1088
- över delarna, rotraden för en bunt lyder `bunt (N delar)`) och klistras in
1089
- som den är, aldrig handskriven. Nämn att `utvärdera rapport` finns
1090
- när underlaget ska läsas sida vid sida. Ingen skrivning mot Asana.
1091
-
1092
- ### Batch: flera poster
1093
-
1094
- `utvärdera <post1> <post2> … <modell…>` kör varje ticket och modell i en EGEN
1095
- `korning.mjs`-process, parallellt enligt `svep.mjs`: två tickets åt gången, högst
1096
- fyra modeller för en ticket i flykt, och nästa ticket startar när en ticket är
1097
- helt klar (steg 4). Raden `svep.mjs` kör per varv:
1094
+ never by writing the file.
1095
+
1096
+ 5. **DELA_UPP in the experiment.** A DELA_UPP outcome follows bedöm's ordinary
1097
+ split rules but without a pause for a reviewer (in v1 `korning.mjs` does not run
1098
+ the split itself, see step 4; the rules here apply when it runs):
1099
+ part directories
1100
+ `<modell>/k<N>/del-1/ … del-N/` with `split` set, the parent's `spec.md` +
1101
+ `granskarblock.md` copied in, a new analysis + score per part. If a
1102
+ bundle part is split, it becomes `del-N/del-M/` under that part, never a new
1103
+ sibling in the root (`bundle` and `split` are never set on the same directory).
1104
+ A part without a ceiling becomes `OVER_SKALAN` and is never split further. Other
1105
+ stop outcomes (`TILLBAKA_TILL_SPEC`, `KONCEPT`, `DIAGNOS`, `ESKALERA`,
1106
+ `INPUT_SAKNAS`) require a human or the customer: archive the outcome, set
1107
+ the run's status to `"stopp"` via `archive.mjs experiment`, abort that
1108
+ round, make it visible in the report when it runs, and the other runs in
1109
+ the list continue independently.
1110
+
1111
+ **OLIKA waits for a ruling.** If the double run writes OLIKA in a round:
1112
+ archive per bedöm's rules (the hold stands), set the run's status to
1113
+ `"väntar på dom"` via `archive.mjs experiment`, visible in the report when
1114
+ it runs.
1115
+ The ruling is the human's even in the experiment: `döm` is run against the run directory
1116
+ when the reviewer wants (addressed via its path, see `döm`), and no ruling is
1117
+ simulated by the task. The ruling itself does not rewrite `experiment.json`;
1118
+ `svep.mjs` does that (via `archive.mjs experiment`) the next time `utvärdera`
1119
+ is run for the same item (see
1120
+ Resumable in the Batch section below for how and when the status goes
1121
+ to `"klar"` and the report can show the outcome). The other runs in the list
1122
+ continue independently regardless of when, or whether, the ruling is made.
1123
+
1124
+ 6. **Report.** BLUF in the chat: done or unfinished per run, stop outcomes,
1125
+ and the path to `experiments/<slug>/`. The table per run comes from
1126
+ `node <skillkatalog>/scripts/archive.mjs bluf <modell>/k<N>` (recursively
1127
+ over the parts, the root row for a bundle reads `bunt (N delar)`) and is pasted
1128
+ as it is, never handwritten. Mention that `utvärdera rapport` exists
1129
+ when the material is to be read side by side. No write against Asana.
1130
+
1131
+ ### Batch: several items
1132
+
1133
+ `utvärdera <item1> <item2> … <model…>` runs every ticket and model in a SEPARATE
1134
+ `korning.mjs` process, in parallel per `svep.mjs`: two tickets at a time, at most
1135
+ four models for one ticket in flight, and the next ticket starts when a ticket is
1136
+ completely done (step 4). The line `svep.mjs` runs per round:
1098
1137
 
1099
1138
  ```
1100
1139
  node <skillkatalog>/scripts/korning.mjs --katalog <k<N>> --exp <experiments/<slug>> --repo-rot <abs> --request-id <gid> --modell <modell>
1101
1140
  ```
1102
1141
 
1103
- Underlaget läses från disk av `korning.mjs`; ingenting går via stdin till
1104
- orkestreringen, bara till agentprocesserna. Vilka rader varje agentprompt bär
1105
- (ÖNSKEMÅL, KÄLLOR, REPO-ROT, DEPS, SPRÅK, RUBRIKER, ESKALERAD, REQUEST_ID) bor
1106
- i scriptet och dess tester.
1107
-
1108
- Det finns ingen orkestratormodell: agenternas modeller styrs av definition plus
1109
- `--model`-override och verifieras per steg mot `modelUsage` i råsvaret, aldrig
1110
- av svansraden ensam.
1111
-
1112
- `--om` och `--hoppa-över` är svepets egna flaggor; `korning.mjs` frågar aldrig
1113
- något och kan därför inte hänga. `--hoppa-över` är dessutom precis
1114
- återupptagningens semantik, ett avbrutet svep som startas om plockar upp där
1115
- det stannade i stället för att betala om för allt färdigt.
1116
-
1117
- `--katalog` pekar ut den körningskatalog som `archive.mjs ny-korning`
1118
- redan allokerat åt den ticketen och modellen. `korning.mjs` arkiverar
1119
- bara dit, aldrig någon annanstans.
1120
-
1121
- Svepets loop är startaren, och den heter `svep.mjs`: den bär startarens hela
1122
- ansvar per rad (`ny-korning`, tidtagningen som skriver `matning.json`,
1123
- `archive.mjs proveniens` mot körningskatalogen när processen är klar,
1124
- modellverifieringen och status via `archive.mjs experiment`), se steg 4.
1125
-
1126
- - **En ticket, en modell = en `korning.mjs`-process; varje agentsteg är ett
1127
- eget kontextfönster.** Allt tillstånd bor i arkivet på
1128
- disk; `svep.mjs` skriver en rad per ticket och modell när den startar och
1129
- när den slutar (status, väggtid, USD) och en sluttabell, och huvudsessionen
1130
- samlar bara det. `/clear` mellan tickets behövs inte — processgränsen är
1131
- motsvarigheten. Headless-körningen förutsätter att uppgiften kan köra utan
1132
- interaktiva permission-frågor, vilket håller: den läser repot och skriver
1133
- enbart under `.haus-workflow/requests/`. Asana-hämtningen gjordes redan i
1134
- steg 1 av huvudkonversationen, som skrev `onskemal.md`, så `korning.mjs`
1135
- behöver ingen MCP-kontakt alls.
1136
- - **Kör vidare vid fel.** En ticket vars underlag saknas (`onskemal.md`,
1137
- `kallmanifest.md` eller `experiment.json` med `request_id`) märks och hoppas
1138
- över; en vars process kraschar blir `"ofärdig"` med orsak. Resten av svepet
1139
- fortsätter, och `svep.mjs` avslutar med exit 1 när någon körning blev
1140
- `"ofärdig"`, annars 0.
1141
- - **Återupptagbart:** det gör `svep.mjs`: innan en körning startas
1142
- kontrolleras arkivet, styrt av
1143
- körningens `status` i `experiment.json`. `"klar"` och `"stopp"` hoppas
1144
- över med en notis. `"ofärdig"` är avbruten data, inget resultat värt att
1145
- skydda: den utlöser aldrig omkörningsfrågan, utan körs om automatiskt som
1146
- en ny `k<N+1>` under samma modell. Den gamla ofärdiga körningen skrivs
1147
- ändå aldrig över, den står kvar som historik över vad som gick fel. Ett
1148
- undantag: en `"ofärdig"`-rad som bär en `pid` vars process lever är ingen
1149
- avbruten körning utan en som pågår i ett annat svep (två konton mot samma
1150
- checkout, ADR-0093); den hoppas med notisen att den pågår i en annan
1151
- process och står som `pågår` i sluttabellen, aldrig som en dubblett
1152
- (#516). En död pid, eller ingen pid alls, körs om som förut.
1153
- `"väntar på dom"` OMVÄRDERAS i stället för att hoppas över, det är den
1154
- enda platsen som är aktören för den statusen: kör `archive.mjs sparr` på
1155
- körningskatalogen. Exit 0 betyder att domen är fälld: sätt körningens
1156
- status till `"klar"` via `archive.mjs experiment` och fortsätt där den
1157
- stannade (rapporten kan köras separat när alla körningar för posten är
1158
- klara). Exit 1 betyder att den fortfarande väntar: notera det och gå vidare
1159
- till nästa varv. `döm` fäller domen men rör aldrig `experiment.json` (se
1160
- `döm`), det är bara genom att `utvärdera` körs igen för samma post som
1161
- statusen uppdateras. Omkörningsfrågan (se `utvärdera` ovan) gäller alltså bara
1162
- `"klar"`-körningar: det är resultat värda att skydda mot ett fingerfel,
1163
- en ofärdig körning har inget att skydda. Körningar är billiga
1164
- relativt risken att lappa ihop halva resultat. Ett svep över många tickets
1165
- kan därmed fortsätta i en senare session utan onödiga omkörningar.
1166
-
1167
- ### Sammanställning
1168
-
1169
- `utvärdera sammanställ` läser alla `experiments/*/` och skriver
1170
- `experiments/sammanstallning.md`: tabell per ticket med en rad per modell som
1171
- bär poäng, delnings- och stopputfall, plus avvikelsemarkering där
1172
- modellerna skiljer sig. Modellens rad tas ur dess GÄLLANDE körning, den högsta
1173
- `k<N>` vars status i `experiment.json` är `"klar"`. En modell utan klar körning
1174
- representeras av sin senaste `k<N>` och märks `ofullständig` tillsammans med
1175
- den körningens status. Halvfärdiga kataloger märks likadant, aldrig tyst
1176
- utelämnade. Bara läsning — inga agentdispatcher, inga Asana-anrop.
1177
-
1178
- ### Rapport
1179
-
1180
- `utvärdera rapport [--ut <fil>]` renderar en HTML-rapport över experimentytan
1181
- med `scripts/report.mjs`. Rapporten täcker ALLTID hela ytan; något postfilter
1182
- finns inte, menyn i rapporten är där läsaren väljer post. Utan `--ut` skrivs
1183
- HTML på stdout, annars till filen. Publicera den som artefakt när den ska
1184
- läsas.
1185
-
1186
- **Rapporten skrivs ALDRIG automatiskt.** Ingen körning genererar den när den
1187
- blir klar. Den kostar nästan ingenting att regenerera, så det finns ingen
1188
- anledning att skriva den på spekulation, och en fil som ingen bett om är en fil
1189
- ingen litar på.
1190
-
1191
- Rapporten är ren läsning: inga agentdispatcher, inga Asana-anrop, inga
1192
- skrivningar i arkivet utöver utdatafilen.
1193
-
1194
- Vad den visar: en meny över poster med ticketens titel, dess katalogslug och
1195
- vilka modeller posten har, och per vald post en kolumn per körning. Varje delpost ur
1196
- ett DELA_UPP-utfall är en egen kolumn efter sin körning (`opus / k1 / del-1`),
1197
- eftersom det är där utfallen bor när en post delat sig. Sektionerna är fältvisa,
1198
- samma fält på samma rad tvärs kolumnerna: utfall, poängens inringning med
1199
- golv, tak, steg och drivers,
1200
- specens fyra fält, samt `impact.md` och `granskarblock.md` hopfällda per
1201
- körning. Äldre körningar med klassningsfiler (före ADR-0098) får dessutom
1202
- sin Klassning-sektion renderad som förut — kalibreringshistoriken kastas inte.
1203
- Sist står kostnaden (USD, tokens, väggtid, dispatcher) och proveniensen.
1204
- En delkolumn saknar egen mätning och säger `ingår i k<N>`, aldrig omätt.
1205
- Matrisen är ojämn med flit: en post kan ha två modeller och en annan tre,
1206
- eftersom en dyrare modell läggs till bara där de billigare inte räckte.
1207
-
1208
- Vad den ALDRIG gör: rangordnar modeller, rekommenderar en nivå, räknar aggregat
1209
- tvärs poster med olika modelluppsättning, eller uttalar sig om vilket utfall
1210
- som var korrekt. Bedömningen av vad som är ett acceptabelt utfall är
1211
- människans, gjord på erfarenhet av systemet. Rapporten visar spridningen.
1212
-
1213
- ## Avslutning: var posten står
1214
-
1215
- Uppgifterna `speca`, `bedöm`, `godkänn`, `underkänn` och `döm` avslutas
1216
- ALLTID med det här blocket (#530). Ett svar som slutar utan det lämnar
1217
- användaren att gissa var posten står, vad hon ska göra och vilket kommando
1218
- som kör vidare, och arkiv och bräda kan då gå isär tyst (`spec_draft`
1219
- lokalt, tom Offer status på brädan). Blocket består av fyra delar, i ordning:
1220
-
1221
- 1. **Tabellen ur `node <skillkatalog>/scripts/archive.mjs nasta-steg <katalog>`**
1222
- klistras in som den är (`| post | står | väntar på | nästa |`, en rad per
1223
- katalog och del), ALDRIG handskriven, av samma skäl som `bluf`: en
1224
- handskriven avslutning tappar exakt den rad som skulle ha sagt vad som
1225
- väntar. Scriptet räknar ut raderna ur `state.json` (status, `stopp`,
1226
- `respec_varv`, `approval`, olyft OLIKA via spärren, scorerns gällande
1227
- utfall ur prisfilen) med reglerna ur den här skillens egna grindar;
1228
- regeltabellen med källhänvisning per rad står ovanför funktionen
1229
- `nastaStegFor` i scriptet. Ett läge scriptet inte känner skrivs som
1230
- `okänt läge: <status>`, aldrig som en gissning.
1231
- 2. **Brädraden.** Har posten ett gid: hämta Offer status via request-sync i
1232
- huvudkonversationen (aldrig ur minnet) och skriv raden
1233
- `Bräda: <Offer status>` direkt under tabellen. Saknar posten gid: skriv
1142
+ The source material is read from disk by `korning.mjs`; nothing goes via stdin to
1143
+ the orchestration, only to the agent processes. Which lines every agent prompt carries
1144
+ (ÖNSKEMÅL, KÄLLOR, REPO-ROT, DEPS, SPRÅK, RUBRIKER, ESKALERAD, REQUEST_ID) lives
1145
+ in the script and its tests.
1146
+
1147
+ There is no orchestrator model: the agents' models are steered by the definition plus
1148
+ the `--model` override and verified per step against `modelUsage` in the raw reply, never
1149
+ by the tail line alone.
1150
+
1151
+ `--om` and `--hoppa-över` are the sweep's own flags; `korning.mjs` never asks
1152
+ anything and can therefore not hang. `--hoppa-över` is moreover exactly
1153
+ the resumption's semantics: an interrupted sweep that is restarted picks up where
1154
+ it stopped instead of paying again for everything finished.
1155
+
1156
+ `--katalog` points out the run directory that `archive.mjs ny-korning`
1157
+ has already allocated for that ticket and model. `korning.mjs` archives
1158
+ only there, never anywhere else.
1159
+
1160
+ The sweep's loop is the starter, and it is called `svep.mjs`: it carries the starter's whole
1161
+ responsibility per row (`ny-korning`, the timing that writes `matning.json`,
1162
+ `archive.mjs proveniens` against the run directory when the process is done,
1163
+ the model verification and status via `archive.mjs experiment`), see step 4.
1164
+
1165
+ - **One ticket, one model = one `korning.mjs` process; every agent step is a
1166
+ context window of its own.** All state lives in the archive on
1167
+ disk; `svep.mjs` writes one line per ticket and model when it starts and
1168
+ when it ends (status, wall time, USD) and a final table, and the main session
1169
+ gathers only that. `/clear` between tickets is not needed: the process boundary is
1170
+ the equivalent. The headless run presupposes that the task can run without
1171
+ interactive permission questions, which holds: it reads the repo and writes
1172
+ only under `.haus-workflow/requests/`. The Asana fetch was already done in
1173
+ step 1 by the main conversation, which wrote `onskemal.md`, so `korning.mjs`
1174
+ needs no MCP contact at all.
1175
+ - **Continue on errors.** A ticket whose source material is missing (`onskemal.md`,
1176
+ `kallmanifest.md` or `experiment.json` with `request_id`) is marked and
1177
+ skipped; one whose process crashes becomes `"ofärdig"` with a reason. The rest of the sweep
1178
+ continues, and `svep.mjs` ends with exit 1 when any run became
1179
+ `"ofärdig"`, otherwise 0.
1180
+ - **Resumable:** `svep.mjs` does that: before a run is started,
1181
+ the archive is checked, steered by
1182
+ the run's `status` in `experiment.json`. `"klar"` and `"stopp"` are skipped
1183
+ with a notice. `"ofärdig"` is interrupted data, not a result worth
1184
+ protecting: it never triggers the rerun question, but is rerun automatically as
1185
+ a new `k<N+1>` under the same model. The old unfinished run is still
1186
+ never overwritten, it stays as history of what went wrong. One
1187
+ exception: an `"ofärdig"` row that carries a `pid` whose process is alive is not an
1188
+ interrupted run but one that is in progress in another sweep (two accounts against the same
1189
+ checkout, ADR-0093); it is skipped with the notice that it is in progress in another
1190
+ process and stands as `pågår` in the final table, never as a duplicate
1191
+ (#516). A dead pid, or no pid at all, is rerun as before.
1192
+ `"väntar på dom"` is RE-EVALUATED instead of skipped; it is the
1193
+ only place that is the actor for that status: run `archive.mjs sparr` on
1194
+ the run directory. Exit 0 means that the ruling has been made: set the run's
1195
+ status to `"klar"` via `archive.mjs experiment` and continue where it
1196
+ stopped (the report can be run separately when all runs for the item are
1197
+ done). Exit 1 means that it is still waiting: note it and move on
1198
+ to the next round. `döm` makes the ruling but never touches `experiment.json` (see
1199
+ `döm`); it is only by `utvärdera` being run again for the same item that
1200
+ the status is updated. The rerun question (see `utvärdera` above) thus applies only to
1201
+ `"klar"` runs: those are results worth protecting against a slip of the finger,
1202
+ an unfinished run has nothing to protect. Runs are cheap
1203
+ relative to the risk of patching together half results. A sweep over many tickets
1204
+ can thereby continue in a later session without unnecessary reruns.
1205
+
1206
+ ### Compilation
1207
+
1208
+ `utvärdera sammanställ` reads all `experiments/*/` and writes
1209
+ `experiments/sammanstallning.md`: a table per ticket with one row per model that
1210
+ carries the score, split and stop outcomes, plus a deviation marking where
1211
+ the models differ. A model's row is taken from its CURRENT run, the highest
1212
+ `k<N>` whose status in `experiment.json` is `"klar"`. A model without a done run
1213
+ is represented by its latest `k<N>` and marked `ofullständig` together with
1214
+ that run's status. Half-finished directories are marked the same way, never silently
1215
+ left out. Reading only: no agent dispatches, no Asana calls.
1216
+
1217
+ ### Report
1218
+
1219
+ `utvärdera rapport [--ut <fil>]` renders an HTML report over the experiment area
1220
+ with `scripts/report.mjs`. The report ALWAYS covers the whole area; there is no item
1221
+ filter, the menu in the report is where the reader chooses an item. Without `--ut` the
1222
+ HTML is written to stdout, otherwise to the file. Publish it as an artifact when it is to be
1223
+ read.
1224
+
1225
+ **The report is NEVER written automatically.** No run generates it when it
1226
+ is done. It costs almost nothing to regenerate, so there is no
1227
+ reason to write it on speculation, and a file nobody asked for is a file
1228
+ nobody trusts.
1229
+
1230
+ The report is pure reading: no agent dispatches, no Asana calls, no
1231
+ writes in the archive beyond the output file.
1232
+
1233
+ What it shows: a menu over items with the ticket's title, its directory slug and
1234
+ which models the item has, and per chosen item a column per run. Every sub-item from
1235
+ a DELA_UPP outcome is a column of its own after its run (`opus / k1 / del-1`),
1236
+ since that is where the outcomes live when an item has split. The sections are field-wise,
1237
+ the same field on the same row across the columns: outcome, the score's bracketing with
1238
+ floor, ceiling, steps and drivers,
1239
+ the spec's four fields, plus `impact.md` and `granskarblock.md` collapsed per
1240
+ run. Older runs with classification files (before ADR-0098) also get
1241
+ their Klassning section rendered as before: the calibration history is not thrown away.
1242
+ Last come the cost (USD, tokens, wall time, dispatches) and the provenance.
1243
+ A part column lacks a measurement of its own and says `ingår i k<N>`, never unmeasured.
1244
+ The matrix is uneven on purpose: one item can have two models and another three,
1245
+ since a more expensive model is added only where the cheaper ones were not enough.
1246
+
1247
+ What it NEVER does: rank models, recommend a level, compute aggregates
1248
+ across items with different model sets, or state which outcome
1249
+ was correct. The judgment of what is an acceptable outcome is
1250
+ the human's, made on experience of the system. The report shows the spread.
1251
+
1252
+ ## Closing: where the item stands
1253
+
1254
+ The tasks `speca`, `bedöm`, `godkänn`, `underkänn` and `döm` ALWAYS end
1255
+ with this block (#530). A reply that ends without it leaves
1256
+ the user to guess where the item stands, what they should do and which command
1257
+ runs on, and the archive and the board can then drift apart silently (`spec_draft`
1258
+ locally, empty Offer status on the board). The block consists of four parts, in order:
1259
+
1260
+ 1. **The table from `node <skillkatalog>/scripts/archive.mjs nasta-steg <katalog>`**
1261
+ is pasted as it is (`| post | står | väntar på | nästa |`, one row per
1262
+ directory and part), NEVER handwritten, for the same reason as `bluf`: a
1263
+ handwritten closing drops exactly the row that would have said what is
1264
+ waiting. The script computes the rows from `state.json` (status, `stopp`,
1265
+ `respec_varv`, `approval`, an unlifted OLIKA via the hold, the scorer's current
1266
+ outcome from the pricing file) with the rules from this skill's own gates;
1267
+ the rule table with a source reference per row stands above the function
1268
+ `nastaStegFor` in the script. A state the script does not know is written as
1269
+ `okänt läge: <status>`, never as a guess.
1270
+ 2. **The board line.** If the item has a gid: fetch Offer status via request-sync in
1271
+ the main conversation (never from memory) and write the line
1272
+ `Bräda: <Offer status>` directly under the table. If the item lacks a gid: write
1234
1273
  `Bräda: ingen (posten ligger inte på brädan)`.
1235
- 3. **ISÄR-kontrollen.** Arkivstatus och Offer status ska motsvara varandra
1236
- enligt tabellen nedan (värdena är request-sync:s, normaliserade på
1237
- alfabetiska kärnan som där). Avviker de skrivs en rad
1238
- `ISÄR: arkiv <x>, bräda <y>` följd av det kommando som stänger gapet.
1239
-
1240
- | arkivstatus (`state.json`) | förväntad Offer status | stänger gapet |
1241
- | ---------------------------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------- |
1242
- | `spec_draft` | tom eller 📥 Inkommet | sync via request-sync (🔍) när specen ska granskas |
1243
- | `spec_synced` | 🔍 Spec redo för granskning | `godkänn`/`underkänn`, eller sync om brädan står på 📥 |
1244
- | `spec_synced`/`spec_draft` med `approval` satt | ✅ Spec ok | `bedöm`; står brädan på 🔍 är approval-anteckningen äldre än brädan, läs om grinden |
1245
- | `assessed`/`priced`, poängen synkad | ⚖️ Poängsättning redo för granskning | sync via request-sync om brädan står kvar på ✅ |
1246
- | klar (`godkänn` från ⚖️) | 📤 Klar – till kund | inget, kundens `Decision` avgör resten |
1247
-
1248
- Arkivet är cache och Asana sanningen (Hårda regler): ett ISÄR löses alltid
1249
- i riktning mot brädan, aldrig genom att skriva om brädan för att matcha
1250
- arkivet. En experimentvariant (`experiment: true`) har ingen brädrad och
1251
- ingen ISÄR-kontroll, den når aldrig Asana.
1252
-
1253
- 4. **Valet, när det finns ett.** Ett stopputfall (`TILLBAKA_TILL_SPEC`,
1254
- `ESKALERA`, `KONCEPT`, `DIAGNOS`) och en olyft `double_run: olika`
1255
- presenteras som ett val användaren ska ta, inte bara som routing:
1256
- "här är ditt val", följt av alternativen (vid TILLBAKA_TILL_SPEC det
1257
- öppna valet ur `stopp.orsak` eller källfilen; vid OLIKA de två
1258
- körningarnas belägg sida vid sida, se bedöm steg 6) och kommandot som
1259
- verkställer svaret (`speca <post>` efter `## Avgjorda val` + `respec`,
1260
- `döm <post>`, kunddialog). Ett stopp som bara routas läses som "maskinen
1261
- jobbar vidare", och ingen gör något.
1262
-
1263
- ## Mekaniska script: `scripts/`
1264
-
1265
- Tolv deterministiska script bor i katalogen: de fem kontrollerna nedan,
1266
- `kontroll.mjs` som kör scorerns kontrollkedja i en process,
1267
- `archive.mjs` som äger arkivets kataloger, spärrar och `experiment.json`,
1268
- `svep.mjs` som är utvärderas startare,
1269
- `korning.mjs` som är utvärderas inre process, `report.mjs` som
1270
- renderar modellrapporten (se `utvärdera rapport`), `lib.mjs` med de delade
1271
- byggstenarna och `ref-hash.mjs` för stabil hashning. Kontrollerna ersätter
1272
- modellkörningar där kontrollen är ren form,
1273
- billigare, snabbare och utan omdömesglidning. Varje script bär sha256 för de
1274
- referensfiler vars regler det speglar och VARNAR när en katalogbump ändrat dem
1275
- (kontraktsdrift ska upptäckas av en check, inte av en kund):
1276
-
1277
- - `scripts/precheck.mjs <arkivkatalog> [--impact]` — formkontroll FÖRE dyra
1278
- dispatcher: rubriker, exakt ett specblock, 2–5 punkter, förbjuden vokabulär,
1279
- granskarblocksrader, state.json-fält. Skiljer på BRISTER (exit 1, stoppar)
1280
- och VARNING (heuristik — visas, stoppar inte). Rubrikerna matchas via
1281
- aliastabellen i `lib.mjs` på svenska eller engelska; rubriker ur båda
1282
- språken i samma spec är en BRIST. Kandidatformen känns igen på markörraden
1283
- (`**Koncept-kandidat**`/`**Diagnos-kandidat**`, eller de engelska
1284
- motsvarigheterna) och skrivs som `KANDIDAT: KONCEPT` eller
1285
- `KANDIDAT: DIAGNOS` på stdout, alltid, även vid exit 1; en kandidat med en
1286
- Lösning är en BRIST. Med `--impact` läses etiketten på analysens rad
1287
- `**Avvikelse mot specen:**` och skrivs som `AVVIKELSE: ingen`,
1288
- `AVVIKELSE: precisering` eller `AVVIKELSE: motsägelse`; en rad utan
1289
- etikett är en BRIST. Orkestratorn läser kandidat och avvikelse ur de
1290
- raderna, aldrig ur filerna själv.
1291
- - `scripts/resolve-anchors.mjs <run.json> <spec.md> [impact.md]` — löser
1292
- beläggsankare (`belagg_ankare`/`ankare`) till ordagranna källblock, körs
1293
- FÖRE contract-check. Exit 0 = allt löst (eller inga ankare i filen), 1 =
1294
- minst ett OLÖST/FLERTYDIG ankare (kontraktsbrott, mekanisk omdispatch),
1295
- 2 = användningsfel.
1296
- - `scripts/contract-check.mjs <run.json> <spec.md> [impact.md] [--expect-id <id>] [--state <state.json>] [--sprak <svenska|engelska>]`
1297
- — kontraktskontroll av score-JSON EFTER körning: enums,
1298
- konfidensintervall, poängupplösning, inringningens närvaro (även per
1299
- alternativ; under-A1-fallet med floor null och tak A1 är giltigt), belägg/
1300
- driver-citat som ordagranna substrängar av underlaget, protokollterm-
1301
- svartlista i kundtextfält, sluten kuvertnyckellista, request_id mot katalogen.
1302
- INPUT_SAKNAS kräver saknas-lista i stället för frågerader. Ett olöst ankare
1303
- (belägg saknas trots `belagg_ankare`, eller `ankare` utan `citat`) är
1304
- dispatcherns fel, inte agentens: exit 2 med hänvisning till resolve-anchors.mjs.
1305
- Stegschemat: varje `bracket.steps[i]` (även per alternativ) är exakt
1306
- `{anchor, relation, grund}` med `relation` ur `storre`, `mindre`, `lika`;
1307
- egna nycklar och tomma grunder är BROTT. Motsägelsegrinden: ges `impact.md`
1308
- och analysens `**Avvikelse mot specen:**` bär `motsägelse` är varje annat
1309
- utfall än TILLBAKA_TILL_SPEC ett BROTT. Prövar dessutom inringningens
1310
- konsistens (golv före tak, between angränsande och inuti golv–tak, poäng
1311
- mellan between-paret, relation mot poäng; poäng PÅ ett ankare trots
1312
- storre/mindre är VARNING bara vid avrundningskollaps), att `type` är en av
1313
- skalans fem typer, och undantar svartlistade ord som står i specen
1314
- (ADR-0115). Med `--sprak` prövas varje kundtextfält mot språket (#521):
1315
- minst två OLIKA funktionsord ur fel språk, hela ord, skiftlägesokänsligt
1316
- (listorna i `lib.mjs` `FUNKTIONSORD`), är BROTT `kundtext på fel språk`;
1317
- diakritiska tecken är ingen signal. Utan flaggan ingen språkkontroll.
1318
- - `scripts/check-lasta.mjs "<LÄSTA-innehåll>" <spec.md> <impact.md>` —
1319
- kontrollerar scorerns läs-scope: LÄSTA-raden ska bära underlagens två
1320
- sökvägar och får därutöver bara bära filer under skillens egen katalog.
1321
- Exit 0 = ok, 1 = kontraktsbrott (BROTT-rader), 2 = användningsfel.
1322
- - `scripts/compare-scores.mjs <a.json> <b.json>` — deterministisk
1323
- dubbelkörningsdiff: outcome,
1324
- score, confidence, golv- och takankare. Aldrig prosa. Skriver `LIKA`,
1325
- `LIKA_TOLERANS` (poängskillnad ≤ 0,5 med samma outcome, oavsett confidence,
1326
- och ankarband som överlappar eller angränsar; exit 0, verdiktraden pekar ut
1327
- körningen med högst poäng) eller `OLIKA` + diffrader; exit 0/1, exit 2 =
1328
- ojämförbara körningar (dispatcherfel, aldrig LIKA). Diffar även de inre
1329
- stegen: samma ankare i `bracket.steps` med `storre` i ena körningen och
1330
- `mindre` i den andra ger diffraden
1331
- `bracket.steps[A<n>].relation: "mindre" ↔ "storre"`, en hård diff som
1332
- aldrig faller under toleransen; golv- och takankare i någon av körningarna
1333
- undantas, de flippar av konstruktion när banden angränsar. Scriptet kräver
1334
- mode `pricing`; guard-jämförelser är en medveten utbyggnad i guard-steget.
1335
- Angränsning avgörs av skalans ordning (lib.mjs ANKARE, A17 mellan A12 och
1336
- A13), och utöver poäng och ankare diffas `type` (toleransrad), antalet
1337
- `conditions` (hård diff) och `alternatives` per label (ADR-0115).
1338
- - `scripts/archive.mjs <kommando> <katalog> [flaggor]`: arkivets mekanik
1339
- (fullständig lista i scriptets huvud): `add-run`, `next-n`, `sparr`,
1340
- `utslag`, `set-status`, `kalibrering`, `ny-korning`, `proveniens`, samt
1274
+ 3. **The ISÄR check.** The archive status and the Offer status must correspond to each other
1275
+ per the table below (the values are request-sync's, normalized on
1276
+ the alphabetic core as there). If they differ, a line
1277
+ `ISÄR: arkiv <x>, bräda <y>` is written, followed by the command that closes the gap.
1278
+
1279
+ | archive status (`state.json`) | expected Offer status | closes the gap |
1280
+ | ---------------------------------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------- |
1281
+ | `spec_draft` | empty or 📥 Inkommet | sync via request-sync (🔍) when the spec is to be reviewed |
1282
+ | `spec_synced` | 🔍 Spec redo för granskning | `godkänn`/`underkänn`, or sync if the board stands at 📥 |
1283
+ | `spec_synced`/`spec_draft` with `approval` set | ✅ Spec ok | `bedöm`; if the board stands at 🔍, the approval note is older than the board, reread the gate |
1284
+ | `assessed`/`priced`, the score synced | ⚖️ Poängsättning redo för granskning | sync via request-sync if the board still stands at ✅ |
1285
+ | done (`godkänn` from ⚖️) | 📤 Klar – till kund | nothing, the customer's `Decision` decides the rest |
1286
+
1287
+ The archive is a cache and Asana the truth (Hard rules): an ISÄR is always resolved
1288
+ in the direction of the board, never by rewriting the board to match
1289
+ the archive. An experiment variant (`experiment: true`) has no board line and
1290
+ no ISÄR check; it never reaches Asana.
1291
+
1292
+ 4. **The choice, when there is one.** A stop outcome (`TILLBAKA_TILL_SPEC`,
1293
+ `ESKALERA`, `KONCEPT`, `DIAGNOS`) and an unlifted `double_run: olika`
1294
+ are presented as a choice the user must make, not just as routing:
1295
+ "here is your choice", followed by the alternatives (for TILLBAKA_TILL_SPEC the
1296
+ open choice from `stopp.orsak` or the source file; for OLIKA the two
1297
+ runs' evidence side by side, see bedöm step 6) and the command that
1298
+ carries out the answer (`speca <item>` after `## Avgjorda val` + `respec`,
1299
+ `döm <item>`, customer dialogue). A stop that is only routed is read as "the machine
1300
+ is working on it", and nobody does anything.
1301
+
1302
+ ## Mechanical scripts: `scripts/`
1303
+
1304
+ Twelve deterministic scripts live in the directory: the five checks below,
1305
+ `kontroll.mjs` which runs the scorer's check chain in one process,
1306
+ `archive.mjs` which owns the archive's directories, holds and `experiment.json`,
1307
+ `svep.mjs` which is utvärdera's starter,
1308
+ `korning.mjs` which is utvärdera's inner process, `report.mjs` which
1309
+ renders the model report (see `utvärdera rapport`), `lib.mjs` with the shared
1310
+ building blocks and `ref-hash.mjs` for stable hashing. The checks replace
1311
+ model runs where the check is pure form:
1312
+ cheaper, faster and without judgment drift. Every script carries sha256 for the
1313
+ reference files whose rules it mirrors and WARNS when a catalog bump has changed them
1314
+ (contract drift must be discovered by a check, not by a customer):
1315
+
1316
+ - `scripts/precheck.mjs <arkivkatalog> [--impact]`: the form check BEFORE expensive
1317
+ dispatches: headings, exactly one spec block, 2–5 bullets, forbidden vocabulary,
1318
+ reviewer block rows, state.json fields. Distinguishes BRISTER (exit 1, stops)
1319
+ from VARNING (a heuristic: shown, does not stop). The headings are matched via
1320
+ the alias table in `lib.mjs` in Swedish or English; headings from both
1321
+ languages in the same spec are a BRIST. The candidate form is recognized by the marker line
1322
+ (`**Koncept-kandidat**`/`**Diagnos-kandidat**`, or the English
1323
+ equivalents) and written as `KANDIDAT: KONCEPT` or
1324
+ `KANDIDAT: DIAGNOS` on stdout, always, even at exit 1; a candidate with a
1325
+ _Lösning_ (_Solution_) is a BRIST. With `--impact` the label on the analysis's row
1326
+ `**Deviation from the spec:**` is read and written as `AVVIKELSE: ingen`,
1327
+ `AVVIKELSE: precisering` or `AVVIKELSE: motsägelse`; a row without a
1328
+ label is a BRIST. The orchestrator reads candidate and deviation from those
1329
+ lines, never from the files itself.
1330
+ - `scripts/resolve-anchors.mjs <run.json> <spec.md> [impact.md]`: resolves
1331
+ evidence anchors (`belagg_ankare`/`ankare`) to verbatim source blocks, runs
1332
+ BEFORE contract-check. Exit 0 = everything resolved (or no anchors in the file), 1 =
1333
+ at least one OLÖST/FLERTYDIG anchor (a contract breach, mechanical redispatch),
1334
+ 2 = usage error.
1335
+ - `scripts/contract-check.mjs <run.json> <spec.md> [impact.md] [--expect-id <id>] [--state <state.json>] [--sprak <svenska|engelska>]`:
1336
+ the contract check of the score JSON AFTER the run: enums,
1337
+ confidence interval, score resolution, the presence of bracketing (also per
1338
+ alternative; the under-A1 case with floor null and ceiling A1 is valid), evidence/
1339
+ driver quotes as verbatim substrings of the source material, the protocol-term
1340
+ blacklist in customer-text fields, a closed envelope key list, request_id against the directory.
1341
+ INPUT_SAKNAS requires a missing list instead of question rows. An unresolved anchor
1342
+ (evidence missing despite `belagg_ankare`, or `ankare` without `citat`) is
1343
+ the dispatcher's error, not the agent's: exit 2 with a reference to resolve-anchors.mjs.
1344
+ The step schema: every `bracket.steps[i]` (also per alternative) is exactly
1345
+ `{anchor, relation, grund}` with `relation` from `storre`, `mindre`, `lika`;
1346
+ keys of its own and empty grounds are BROTT. The contradiction gate: if `impact.md` is given
1347
+ and the analysis's `**Deviation from the spec:**` carries `contradiction`, every
1348
+ outcome other than TILLBAKA_TILL_SPEC is a BROTT. It also tests the bracketing's
1349
+ consistency (floor before ceiling, between adjacent and inside floor–ceiling, a score
1350
+ between the between pair, relation against score; a score ON an anchor despite
1351
+ storre/mindre is a VARNING only on rounding collapse), that `type` is one of
1352
+ the scale's five types, and exempts blacklisted words that stand in the spec
1353
+ (ADR-0115). With `--sprak` every customer-text field is tested against the language (#521):
1354
+ at least two DIFFERENT function words from the wrong language, whole words, case-insensitive
1355
+ (the lists in `lib.mjs` `FUNKTIONSORD`), is BROTT `kundtext på fel språk`;
1356
+ diacritics are no signal. Without the flag, no language check.
1357
+ - `scripts/check-lasta.mjs "<LÄSTA-innehåll>" <spec.md> <impact.md>`:
1358
+ checks the scorer's read scope: the LÄSTA line must carry the source material's two
1359
+ paths and may beyond that carry only files under the skill's own directory.
1360
+ Exit 0 = ok, 1 = contract breach (BROTT rows), 2 = usage error.
1361
+ - `scripts/compare-scores.mjs <a.json> <b.json>`: the deterministic
1362
+ double-run diff: outcome,
1363
+ score, confidence, floor and ceiling anchors. Never prose. Writes `LIKA`,
1364
+ `LIKA_TOLERANS` (a score difference ≤ 0.5 with the same outcome, regardless of confidence,
1365
+ and anchor bands that overlap or are adjacent; exit 0, the verdict line points out
1366
+ the run with the highest score) or `OLIKA` + diff rows; exit 0/1, exit 2 =
1367
+ incomparable runs (a dispatcher error, never LIKA). Also diffs the inner
1368
+ steps: the same anchor in `bracket.steps` with `storre` in one run and
1369
+ `mindre` in the other gives the diff row
1370
+ `bracket.steps[A<n>].relation: "mindre" ↔ "storre"`, a hard diff that
1371
+ never falls under the tolerance; floor and ceiling anchors in either run
1372
+ are exempt, they flip by construction when the bands are adjacent. The script requires
1373
+ mode `pricing`; guard comparisons are a deliberate extension in the guard step.
1374
+ Adjacency is decided by the scale's order (lib.mjs ANKARE, A17 between A12 and
1375
+ A13), and beyond score and anchors, `type` (a tolerance row), the number of
1376
+ `conditions` (a hard diff) and `alternatives` per label (ADR-0115) are diffed.
1377
+ - `scripts/archive.mjs <kommando> <katalog> [flaggor]`: the archive's mechanics
1378
+ (full list in the script's header): `add-run`, `next-n`, `sparr`,
1379
+ `utslag`, `set-status`, `kalibrering`, `ny-korning`, `proveniens`, plus
1341
1380
  `stopp <katalog> --utfall KONCEPT|DIAGNOS|TILLBAKA_TILL_SPEC --kalla spec.md|impact.md`
1342
- (skriver `state.stopp` efter att ha kontrollerat att källan bär utfallet;
1343
- `--kalla impact.md` sätter status `assessed`, `--kalla spec.md` rör inte
1344
- status), `respec <katalog>` (lägger undan `impact.md` som
1345
- `impact-superseded-<datum>.md`, återställer status `spec_draft`,
1346
- `approval`, `granskarutslag` och `stopp`, rör aldrig `runs[]`) och
1347
- `bluf <katalog>` (markdown-tabellen `| post | poäng | utfall | konfidens | anm |`
1348
- över katalogen och dess `del-N/` rekursivt, bunt-roten som
1349
- `bunt (N delar)`; värdena ur filerna, aldrig ur prosa) och
1350
- `nasta-steg <katalog>` (läs-enbart, markdown-tabellen
1351
- `| post | står | väntar på | nästa |` över katalogen och dess `del-N/`
1352
- rekursivt: var posten står, vad en människa väntas göra och vilket
1353
- `/request`-kommando som kör vidare, ur `state.json` och scorerns gällande
1354
- utfall enligt regeltabellen i scriptet; okänt läge skrivs som
1355
- `okänt läge: <status>` med exit 0, trasig `state.json` är exit 2, se
1356
- `## Avslutning: var posten står`). `add-run` läser
1357
- `effort:` ur agentdefinitionen när `--effort` utelämnas och kanoniserar
1358
- `--model`; `set-status priced` vägrar när `stopp` är satt.
1381
+ (writes `state.stopp` after checking that the source carries the outcome;
1382
+ `--kalla impact.md` sets status `assessed`, `--kalla spec.md` does not touch
1383
+ the status), `respec <katalog>` (puts `impact.md` aside as
1384
+ `impact-superseded-<datum>.md`, resets status (to `spec_draft`),
1385
+ `approval`, `granskarutslag` and `stopp`, never touches `runs[]`) and
1386
+ `bluf <katalog>` (the markdown table `| post | poäng | utfall | konfidens | anm |`
1387
+ over the directory and its `del-N/` recursively, the bundle root as
1388
+ `bunt (N delar)`; the values from the files, never from prose) and
1389
+ `nasta-steg <katalog>` (read-only, the markdown table
1390
+ `| post | står | väntar på | nästa |` over the directory and its `del-N/`
1391
+ recursively: where the item stands, what a human is expected to do and which
1392
+ `/request` command runs on, from `state.json` and the scorer's current
1393
+ outcome per the rule table in the script; an unknown state is written as
1394
+ `okänt läge: <status>` with exit 0, a broken `state.json` is exit 2, see
1395
+ `## Closing: where the item stands`). `add-run` reads
1396
+ `effort:` from the agent definition when `--effort` is omitted and canonicalizes
1397
+ `--model`; `set-status priced` refuses when `stopp` is set.
1359
1398
  `experiment <experimentkatalog> [--satt nyckel=värde]... [--variant <modell>]... [--korning <modell>/k<N> --status <klar|ofärdig|stopp|väntar på dom> [--orsak <text>] [--pid <heltal>]]`
1360
- är den ENDA skrivaren av `experiment.json` (se `utvärdera` steg 1): låst
1361
- läs-modifiera-skriv med atomisk skrivning, katalog och fil skapas om de
1362
- saknas. `--satt` skriver toppnivåfält append-only (samma värde är no-op,
1363
- ett annat värde är exit 2; `eskalerad` tolkas som boolean), `--variant` är
1364
- UNION med bevarad ordning, och `--korning` kräver `--status` ur de fyra
1365
- statusliteralerna, rör ingen annan körning och kräver att katalogen
1366
- `<modell>/k<N>` finns. `--pid` (heltal, bara med `--korning`) skriver
1367
- startarens pid på raden; en statusskrivning utan `--pid` ersätter raden
1368
- helt, så pid:n försvinner med slutstatusen (#516). Exit 0 ok, 2
1369
- användnings- eller tillståndsfel.
1399
+ is the ONLY writer of `experiment.json` (see `utvärdera` step 1): a locked
1400
+ read-modify-write with an atomic write; the directory and file are created if they are
1401
+ missing. `--satt` writes top-level fields append-only (the same value is a no-op,
1402
+ another value is exit 2; `eskalerad` is interpreted as a boolean), `--variant` is
1403
+ a UNION with preserved order, and `--korning` requires `--status` from the four
1404
+ status literals, touches no other run and requires that the directory
1405
+ `<modell>/k<N>` exists. `--pid` (an integer, only with `--korning`) writes
1406
+ the starter's pid on the row; a status write without `--pid` replaces the row
1407
+ entirely, so the pid disappears with the final status (#516). Exit 0 ok, 2
1408
+ a usage or state error.
1370
1409
  - `scripts/svep.mjs --repo-rot <abs> --konto <namn> --modell <m>... --exp experiments/<slug>... [--tickets-parallellt 2] [--modeller-parallellt 4] [--om | --hoppa-över]`:
1371
- utvärderas startare (se `utvärdera` steg 4 och Batch). Kör två tickets åt
1372
- gången med högst fyra modeller per ticket i flykt, nästa ticket när en är helt
1373
- klar, och gör per varv `ny-korning`, starten av `korning.mjs`,
1410
+ utvärdera's starter (see `utvärdera` step 4 and Batch). Runs two tickets at
1411
+ a time with at most four models per ticket in flight, the next ticket when one is completely
1412
+ done, and does per round `ny-korning`, the start of `korning.mjs`,
1374
1413
  `stderr.log`, `matning.json`, `proveniens`,
1375
- modellverifiering mot `modelUsage` och status via `archive.mjs experiment`.
1376
- Startraden i `experiment.json` bär svepets pid (#516).
1377
- Återupptar ur `experiment.json`: `klar` och `stopp` hoppas utan `--om`,
1378
- `väntar på dom` prövas med `sparr`, `ofärdig` körs om, utom när raden bär
1379
- en levande `pid` (pågår i ett annat svep, hoppas som `pågår`, #516). En `--exp` utan
1380
- `onskemal.md`, `kallmanifest.md` eller `experiment.json` med `request_id` märks
1381
- och hoppas över. Exit 0 när ingen körning blev `ofärdig`, 1 annars, 2 vid
1382
- användningsfel.
1414
+ model verification against `modelUsage` and status via `archive.mjs experiment`.
1415
+ The start row in `experiment.json` carries the sweep's pid (#516).
1416
+ Resumes from `experiment.json`: `klar` and `stopp` are skipped without `--om`,
1417
+ `väntar på dom` is tested with `sparr`, `ofärdig` is rerun, except when the row carries
1418
+ a living `pid` (in progress in another sweep, skipped as `pågår`, #516). An `--exp` without
1419
+ `onskemal.md`, `kallmanifest.md` or `experiment.json` with `request_id` is marked
1420
+ and skipped. Exit 0 when no run became `ofärdig`, 1 otherwise, 2 on a
1421
+ usage error.
1383
1422
  - `scripts/korning.mjs --katalog <abs k<N>> --exp <abs experimentkatalog> --repo-rot <abs> --request-id <gid eller slug> --modell <modell|default> [--claude <kommando>] [--delar-parallellt 2] [--effort <low|medium|high|xhigh|max|default>]`:
1384
- utvärderas inre process (se `utvärdera` steg 4), ett script utan
1385
- modellturer. Bygger prompterna ur `onskemal.md`, `kallmanifest.md` och
1386
- `experiment.json`, där `sprak` är OBLIGATORISK: saknas den skrivs en
1387
- instruktion på stderr och scriptet avslutar med exit 2 innan någon prompt
1388
- byggs (ADR-0114). Varje agentsteg är en egen `claude -p --agent`-process med
1389
- råsvaret under `k<N>/steg/`, och kontrollerna (precheck, `kontroll.mjs`,
1390
- compare-scores) plus arkiveringen (`archive.mjs`) är scriptanrop.
1391
- Buntens delar körs två åt gången och scorerns två körningar samtidigt;
1392
- `DELA_UPP` körs inte autonomt i v1 utan ger status `stopp` med orsak.
1393
- `--effort` är en override på varje agentprocess, symmetrisk med
1394
- `--modell`: `default` ger ingen flagga och add-run läser definitionen som
1395
- förut; allt annat skickas till processen och till `add-run --effort` som
1396
- begärd, aldrig verifierad (#525). `korning.json` är summan över stegen
1397
- (`korningsmotor`, `steg` med `effort_begard` och `samtidiga_processer` per
1398
- steg, `modellverifiering` plus rapportens vanliga nycklar). Exit 0 när
1399
- körningen gick igenom, 1 när minst en del blev ofärdig, 2 vid
1400
- användningsfel.
1423
+ utvärdera's inner process (see `utvärdera` step 4), a script without
1424
+ model turns. Builds the prompts from `onskemal.md`, `kallmanifest.md` and
1425
+ `experiment.json`, where `sprak` is MANDATORY: if it is missing, an
1426
+ instruction is written on stderr and the script ends with exit 2 before any prompt
1427
+ is built (ADR-0114). Every agent step is a `claude -p --agent` process of its own with
1428
+ the raw reply under `k<N>/steg/`, and the checks (precheck, `kontroll.mjs`,
1429
+ compare-scores) plus the archiving (`archive.mjs`) are script calls.
1430
+ The bundle's parts run two at a time and the scorer's two runs at the same time;
1431
+ `DELA_UPP` is not run autonomously in v1 but gives the status `stopp` with a reason.
1432
+ `--effort` is an override on every agent process, symmetric with
1433
+ `--modell`: `default` gives no flag and add-run reads the definition as
1434
+ before; everything else is passed to the process and to `add-run --effort` as
1435
+ requested, never verified (#525). `korning.json` is the sum over the steps
1436
+ (`korningsmotor`, `steg` with `effort_begard` and `samtidiga_processer` per
1437
+ step, `modellverifiering` plus the report's usual keys). Exit 0 when
1438
+ the run went through, 1 when at least one part became unfinished, 2 on a
1439
+ usage error.
1401
1440
  - `scripts/kontroll.mjs <svar.txt> <spec.md> <impact.md> --expect-id <id> --ut <ren.json> [--fixa-kundtext] [--claude <kommando>] [--fix-modell sonnet] [--fix-fil <rå.json>]`:
1402
- scorerns kontrollkedja i EN process (se `bedöm` steg 5): svansrader,
1403
- JSON-extraktion, deterministiska reparationer, sedan check-lasta,
1404
- resolve-anchors och contract-check i EN process. Reparerar det dispatchern
1405
- redan vet: `request_id`, versaler och gemener på `outcome`, `mode`,
1406
- `confidence` och `bracket.steps[].relation`, sträng till tal på `score`, och
1407
- LÄSTA-radens sökvägsform. Reparationerna rör aldrig poäng, utfall eller
1408
- belägg, och varje reparation skrivs som en `REPARERAT:`-rad som anroparen
1409
- bokför i runs-radens `note`. `--fixa-kundtext` skriver om ett
1410
- protokollterm-brott i ett rent kundtextfält med en billig modell i stället
1411
- för att kosta en hel scorerkörning, högst en runda. `--state <state.json>`
1412
- och `--sprak <svenska|engelska>` går rakt vidare till contract-check
1413
- (split-status respektive kundtextens språk; `korning.mjs` tar språket ur
1414
- `experiment.json`, det manuella flödet ur `SPRÅK:` i `kallmanifest.md`).
1415
- Ett språkbrott är aldrig kundtextfixbart och går till omdispatch (#521).
1416
- stdout bär `MODELL:`,
1417
- `LÄSTA:`, ev. `AVVIKELSE:`, `REPARERAT:`-rader och sist verdiktet. Exit 0 =
1418
- kedjan håller, 1 = BROTT (omdispatch enligt ADR-0092), 2 = användningsfel.
1419
-
1420
- Kontrollerna bedömer aldrig innehåll och returnerar aldrig protokollutfall —
1421
- en post som passerar kan fortfarande stoppas av protokollen själva, och det är
1422
- deras jobb. Vid script-fel (exit 2 eller krasch): visa felet och stanna, kör
1423
- aldrig vidare som om kontrollen passerat.
1424
-
1425
- ## Dispatch-kontrakt (gäller alla tre agenterna)
1426
-
1427
- - Prompten är självbärande: agenten ser inte den här konversationen.
1428
- - **Promptmallar, etiketterna ordagrant:**
1441
+ the scorer's check chain in ONE process (see `bedöm` step 5): tail lines,
1442
+ JSON extraction, deterministic repairs, then check-lasta,
1443
+ resolve-anchors and contract-check in ONE process. Repairs what the dispatcher
1444
+ already knows: `request_id`, upper and lower case on `outcome`, `mode`,
1445
+ `confidence` and `bracket.steps[].relation`, string to number on `score`, and
1446
+ the LÄSTA line's path form. The repairs never touch score, outcome or
1447
+ evidence, and every repair is written as a `REPARERAT:` line that the caller
1448
+ books into the runs row's `note`. `--fixa-kundtext` rewrites a
1449
+ protocol-term breach in a pure customer-text field with a cheap model instead
1450
+ of costing a whole scorer run, at most one round. `--state <state.json>`
1451
+ and `--sprak <svenska|engelska>` go straight on to contract-check
1452
+ (the split status and the customer text's language respectively; `korning.mjs` takes the language from
1453
+ `experiment.json`, the manual flow from `SPRÅK:` in `kallmanifest.md`).
1454
+ A language breach is never fixable by the customer-text fix and goes to redispatch (#521).
1455
+ stdout carries `MODELL:`,
1456
+ `LÄSTA:`, any `AVVIKELSE:`, `REPARERAT:` lines and last the verdict. Exit 0 =
1457
+ the chain holds, 1 = BROTT (redispatch per ADR-0092), 2 = usage error.
1458
+
1459
+ The checks never assess content and never return protocol outcomes:
1460
+ an item that passes can still be stopped by the protocols themselves, and that is
1461
+ their job. On a script error (exit 2 or a crash): show the error and stop, never
1462
+ continue as if the check had passed.
1463
+
1464
+ ## Dispatch contract (applies to all three agents)
1465
+
1466
+ - The prompt is self-contained: the agent does not see this conversation.
1467
+ - **Prompt templates, the labels verbatim:**
1429
1468
 
1430
1469
  `request-specifier`:
1431
1470
 
@@ -1450,14 +1489,14 @@ aldrig vidare som om kontrollen passerat.
1450
1489
  ESKALERAD: <ja|nej>
1451
1490
  ```
1452
1491
 
1453
- `RUBRIKER:` är skelettet på specens språk, literalerna ordagrant. Det
1454
- svenska står ovan; vid `SPRÅK: engelska` tas de sju raderna ur
1455
- rubriktabellen i `request-write-spec/references/specformat.md`
1492
+ `RUBRIKER:` is the skeleton in the spec's language, the literals verbatim. The
1493
+ Swedish one stands above; with `SPRÅK: engelska` the seven lines are taken from
1494
+ the heading table in `request-write-spec/references/specformat.md`
1456
1495
  (Problem, Solution, Done when, Not included, Appearance, Requires,
1457
- Conditions). Fotnot: en kandidat ersätter Lösning och När det är klart med
1458
- markörraden (`**Koncept-kandidat**` eller `**Diagnos-kandidat**`, på
1459
- engelska `**Concept candidate**` eller `**Diagnosis candidate**`), det
1460
- avgör specifiern själv enligt skillen.
1496
+ Conditions). Footnote: a candidate replaces Lösning and När det är klart (Solution and Done when) with
1497
+ the marker line (`**Koncept-kandidat**` or `**Diagnos-kandidat**`, in
1498
+ English `**Concept candidate**` or `**Diagnosis candidate**`); the
1499
+ specifier decides that itself per the skill.
1461
1500
 
1462
1501
  `request-assessor`:
1463
1502
 
@@ -1465,6 +1504,18 @@ aldrig vidare som om kontrollen passerat.
1465
1504
  REPO-ROT: <absolut sökväg>
1466
1505
  DEPS: <installerade | saknas>
1467
1506
  SPRÅK: <svenska | engelska>
1507
+ RUBRIKER:
1508
+ **Affected repos and files**
1509
+ **Data storage**
1510
+ **Integration points**
1511
+ **Flows**
1512
+ **Interfaces**
1513
+ **Verification**
1514
+ **Splittability**
1515
+ **Consequence of failure**
1516
+ **Decisions taken**
1517
+ **Assumptions**
1518
+ **Dependencies**
1468
1519
  KÄLLOR:
1469
1520
  <kallmanifest.md ordagrant, eller "inga">
1470
1521
 
@@ -1475,7 +1526,12 @@ aldrig vidare som om kontrollen passerat.
1475
1526
  <granskarblock.md bytetroget>
1476
1527
  ```
1477
1528
 
1478
- `request-scorer` (underlag per sökväg, ADR-0099 — aldrig inklistrat):
1529
+ `RUBRIKER:` is the analysis's own heading skeleton, the English literals from
1530
+ `IMPACT_HEADINGS` (the analysis is internal, see The language contract).
1531
+ The skeleton is independent of `SPRÅK:`, which steers only the analysis's customer-facing
1532
+ parts, not this internal structure.
1533
+
1534
+ `request-scorer` (source material by path, ADR-0099: never pasted):
1479
1535
 
1480
1536
  ```
1481
1537
  REQUEST_ID: <gid eller slug>
@@ -1487,48 +1543,48 @@ aldrig vidare som om kontrollen passerat.
1487
1543
  PÅVERKANSANALYS: <absolut sökväg till impact.md>
1488
1544
  ```
1489
1545
 
1490
- (guard-läge lägger till `FULLPLAN:` och `TIDIGARE_POANG:` —
1491
- guard-kommandot kommer i ett senare steg.)
1492
-
1493
- `SPRÅK:` är kundtextens språk för assessorn och scorern (#521): samma
1494
- värde som specifiern fick, ur `experiment.json` (`sprak`) i utvärdera och
1495
- ur `SPRÅK:` i `kallmanifest.md` i det manuella flödet. Analysens
1496
- kundvända delar och kuvertets kundtextfält skrivs på det språket;
1497
- `contract-check --sprak` fäller kundtext på fel språk.
1498
-
1499
- - **Svansrader efter huvudinnehållet**, i denna ordning: `MODELL: <modell-id>`
1500
- (alla agenter), `LÄSTA: <kommaseparerade absoluta sökvägar eller "inga">`
1501
- (endast scorern — dess Read är promptbegränsad, raden gör regeln
1502
- granskbar), `AVVIKELSE: <text>` (endast om något i prompten bröt mot agentens
1503
- regler, t.ex. en prislista i underlaget). Svansraderna strippas före varje
1504
- maskinell kontroll och före arkivering; MODELL antecknas i runs-raden,
1505
- AVVIKELSE visas alltid för användaren. LÄSTA kontrolleras MEKANISKT av
1506
- `scripts/check-lasta.mjs` (ADR-0099): raden ska bära underlagens två
1507
- utpekade sökvägar (båda obligatoriska) och får därutöver bara bära filer
1508
- under skillens egen katalog (referensfilen) — allt annat är ett
1509
- kontraktsbrott och behandlas som BROTT i bedöm steg 5 (omdispatch EN
1510
- gång, sedan stopp), aldrig bara en notis.
1511
- - Agentens svar i övrigt är rådata enligt skillens kontrakt (prosa-dokument
1512
- eller JSON), ingen kommentar runt om.
1513
- - **Omdispatch (max EN per agent och varv):** skicka HELA ursprungsprompten
1514
- oförändrad, följd av ett block:
1546
+ (guard mode adds `FULLPLAN:` and `TIDIGARE_POANG:`; the
1547
+ guard command comes in a later step.)
1548
+
1549
+ `SPRÅK:` is the customer text's language for the assessor and the scorer (#521): the same
1550
+ value the specifier got, from `experiment.json` (`sprak`) in utvärdera and
1551
+ from `SPRÅK:` in `kallmanifest.md` in the manual flow. The analysis's
1552
+ customer-facing parts and the envelope's customer-text fields are written in that language;
1553
+ `contract-check --sprak` rejects customer text in the wrong language.
1554
+
1555
+ - **Tail lines after the main content**, in this order: `MODELL: <modell-id>`
1556
+ (all agents), `LÄSTA: <kommaseparerade absoluta sökvägar eller "inga">`
1557
+ (only the scorer: its Read is prompt-restricted, the line makes the rule
1558
+ reviewable), `AVVIKELSE: <text>` (only if something in the prompt broke the agent's
1559
+ rules, e.g. a price list in the source material). The tail lines are stripped before every
1560
+ mechanical check and before archiving; MODELL is noted in the runs row,
1561
+ AVVIKELSE is always shown to the user. LÄSTA is checked MECHANICALLY by
1562
+ `scripts/check-lasta.mjs` (ADR-0099): the line must carry the source material's two
1563
+ pointed-out paths (both mandatory) and may beyond that carry only files
1564
+ under the skill's own directory (the reference file); everything else is a
1565
+ contract breach and is treated as BROTT in bedöm step 5 (redispatch ONCE,
1566
+ then stop), never just a notice.
1567
+ - The agent's reply otherwise is raw data per the skill's contract (a prose document
1568
+ or JSON), no commentary around it.
1569
+ - **Redispatch (max ONE per agent and round):** send the WHOLE original prompt
1570
+ unchanged, followed by a block:
1515
1571
  ```
1516
1572
  RÄTTNINGAR:
1517
1573
  <felraderna från kontrollen, citerade ordagrant>
1518
1574
  ```
1519
- En ny subagent minns inget — enbart felrader utan underlag är obrukbart.
1520
- **Omtaget bokförs alltid (ADR-0099):** det förkastade försöket arkiveras
1521
- med `-avvisad`-suffix (prissättning: `<typ>-pricing-vN-avvisad.json`;
1522
- textsteg: `spec-avvisad-<datum>.md` / `impact-avvisad-<datum>.md`) och får
1523
- en egen runs-rad med kontrollens felrader — två körningar av samma steg
1524
- utan spår av varför är precis den lucka kvittot stänger.
1525
-
1526
- ## Använd inte när
1527
-
1528
- - Användaren vill köra en enskild skill fristående ("skriv bara en spec") —
1529
- invokera den skillen direkt.
1530
- - Frågan gäller Asana-uppsättning ("koppla repot") — det är request-sync:s
1531
- uppsättningsflöde, kör den direkt.
1532
- - Guard-läget ("kör guarden", före första kodrad) — körs tills vidare via
1533
- request-score direkt; ett `guard`-kommando kommer i ett
1534
- senare steg.
1575
+ A new subagent remembers nothing: error lines alone without the source material are useless.
1576
+ **The retake is always booked (ADR-0099):** the rejected attempt is archived
1577
+ with an `-avvisad` suffix (pricing: `<typ>-pricing-vN-avvisad.json`;
1578
+ text steps: `spec-avvisad-<datum>.md` / `impact-avvisad-<datum>.md`) and gets
1579
+ a runs row of its own with the check's error lines: two runs of the same step
1580
+ without a trace of why is exactly the gap the receipt closes.
1581
+
1582
+ ## Do not use when
1583
+
1584
+ - The user wants to run a single skill standalone ("skriv bara en spec"):
1585
+ invoke that skill directly.
1586
+ - The question concerns Asana setup ("koppla repot"): that is request-sync's
1587
+ setup flow, run it directly.
1588
+ - Guard mode ("kör guarden", before the first line of code): runs for now via
1589
+ request-score directly; a `guard` command comes in a
1590
+ later step.