Appendix A: Code Examples: The Artefacts of the Twelve Steps

This appendix holds the complete versions of the artefacts that the twelve steps of Vibe to Dev produce, for the two builds that run through the book: service-point-check at Nordvik Region (builder Hanna Wikström) and claims-doc-check at Hansa Mutual (builder Katarzyna Nowak). Each section names the chapter that owns the artefact, says what the artefact is for and what it must never contain, and gives the Nordvik Region file and the Hansa Mutual file side by side in the same shape, so that a reader can copy the shape and replace the values.

Where a chapter prints an artefact in full, the file below is that file. Where a chapter prints an extract or only describes the artefact, the file below is completed from the technical facts of the two builds (docs/BUILDS.md) and from what the chapters state, and a comment in the file says composed. Where two chapters give different values for the same field, the file follows the earlier chapter and the difference is listed in the final section of this appendix. Every person, record, date and figure is fictional and illustrative. Every YAML block parses and every Python block compiles; the text of the appendix was checked that way before it was committed. Legal references are organisational and architectural implications, not legal advice. No artefact is attack tooling, and none holds personal data: the Hansa Mutual build uses synthetic claims (SYN-0001 onwards) in development, and its production export is read only by the identity of the group pipeline.

How to use this appendix

  1. Start with the build record (A.1, A.2). It is the one file that lists every gate, the artefact that met it, the date and the evidence. Every other artefact is a file that the record points to.
  2. Copy the shape, not the values. Dates, names, counts and limits are those of the two builds. Replace every one, and keep a field only if you can fill it with something that is true of your build.
  3. Keep the oracle outside the assistant. The verification plan (A.9), the review record (A.10) and the mandate (A.7) say who wrote the oracle cases and that the assistant cannot edit them. A plan without that line proves little.
  4. Date every piece of evidence. Each claim has a date and an expiry (valid_until, expires). At Tier C the rule of the book is 90 days or the next change of code, dependency, rules or assistant, whichever comes first.
  5. Lower at once, raise by decision. A mandate may lower a ceiling and never raise it. Level 4 Autonomous is not used for any action class of a builder's assistant.
  6. Check mechanically what can be checked (A.19). The short scripts there reject an intent that is incomplete, a tier that contradicts the answers, an ownership record that is overdue, a package that is not on the policy and a learning plan without a second assessor. They do not replace a person reading the artefact.

Identifier families

Family Artefact Step Owning chapter Section
BLD-NR-*, BLD-HM-* Build record: intent, tier, gates all 4, 5, 6 A.1, A.2
SPC-NR-*, SPC-HM-* Specification record 4 8 A.3, A.4
DSG-* and ADR One-page design and Architecture Decision Record 5 9 A.5
AGENTS.md Project rules file for the assistant 3 7 A.6
MND-NR-BLD-*, MND-HM-BLD-* Builder mandate for the assistant 3 4, 7 A.7
RUN-NR-*, RUN-HM-* Agent run record 6 10 A.8
VER-NR-*, VER-HM-* Verification plan with the independent oracle 7 11 A.9
RVW-NR-*, RVW-HM-* Review record 7 11 A.10
PRV-NR-*, PRV-HM-* and the dependency policy Provenance record 8 12 A.11
pipeline.yml Pipeline definition 9 13 A.12
CHG-NR-*, CHG-HM-* and the rollback note Change record 9 13 A.13
RBK-*, RST-*, alerts Runbook manifest, restore-test record 10 14 A.14
OWN-NR-*, OWN-HM-* Code-ownership record 11 15 A.15
HND-* and TKT-* Handover pack and takeover-test result 12 16 A.16
BLP-*, CMP-* Builder learning plan and competence record all 21 A.17
BREG-*, LANE-* Register entry and builder lane 3, 9, 12 17 A.18
scripts Gate and consistency checks all 5, 6, 12, 15, 21 A.19
AIDR-1200 to AIDR-1299 AI Decision Records (Nordvik Region 1200 to 1249, Hansa Mutual 1250 to 1299) 4 referenced throughout

Records used by the two builds: AIDR-1200 builder workspace (Nordvik Region, 2031-10-06); AIDR-1201 mandate MND-NR-BLD-2031-01 (2031-10-13); AIDR-1203 promotion of service-point-check to Tier C (2031-12-15); AIDR-1250 builder lane (Hansa Mutual, 2031-10-13); AIDR-1251 data protection screening (2031-10-20); AIDR-1252 mandate MND-HM-BLD-2031-01 (2031-10-27).

A.1 Build record: Nordvik Region

Owned by Chapters 4, 5 and 6; one record per build, kept in docs/build-record.yaml. The record carries the intent (Step 1), the tier decision (Step 2) and one entry per gate. It never holds a secret, a production value or personal data. The intent section is the full form of Chapter 5; the tier answers are those of the tier decision of 2031-12-15, which Chapter 6 reads with the classifier of A.19. The gate dates are those of the record in Chapter 4.

id: BLD-NR-2031-0001
name: service-point-check
repository: service-point-check
version: 0.2.0
opened: 2031-09-08
builder: Hanna Wikström
deputy: Elin Berg
manager: Gunilla Roos
intent:
  purpose: >-
    Report every week where the opening hours and service notices of the
    24 service points differ between the citizen portal, the phone-service
    script and the notices spreadsheet.
  user: >-
    The e-services team, who correct the sources, and the service-point
    managers, who read the weekly report.
  problem_now: >-
    The three sources disagree after every change. Residents travel to a
    service point that is closed and then phone the phone service.
  success_measure:
    what: Calls to the phone service categorised "wrong opening hours" per month
    source: Phone-service call log, category count
    baseline: {value: 41, period: 2031-08}
    target: At most 20 a month in the first full month on a schedule
    note: Before-and-after count of one category; no control group
  done:
    - One command compares all 24 service points, SP-01 to SP-24
    - The report names every mismatch with the service point and the field
    - The tool only reads; it never writes to any of the three sources
  non_goals:
    - Correcting the sources automatically
    - The printed leaflet, which is produced from the portal export
    - Opening hours of anything other than the 24 service points
  affected:
    - Service-point managers, whose figures are compared
    - Phone-service agents, who answer the calls
    - Residents, who rely on the published hours
  open_questions:
    - Who owns the notices spreadsheet after a manager leaves?
  gate:
    status: met
    date: 2031-09-10
    read_back_by: Elin Berg
    approved_by: Gunilla Roos
    review_by: 2031-12-09
tier:
  current: C
  answers:                      # as of the decision of 2031-12-15 (AIDR-1203)
    users: units
    data: internal
    money: false
    organisational_record: false
    unattended: true
    ranks_or_decides_about_a_named_person: false
    feeds_a_high_risk_ai_system: false
    security_critical_path: false
    irreversible_on_production_data: false
  history:
    - {tier: B, decided: 2031-09-11, reclassify_if: [runs_on_schedule, used_by_other_units]}
    - {tier: C, decided: 2031-12-15, decision: AIDR-1203}
mandate: MND-NR-BLD-2031-01
gates:
  1_intent:        {status: met, date: 2031-09-10, artefact: "intent (this record)", evidence: "read and confirmed by the phone-service lead and Gunilla Roos"}
  2_tier:          {status: met, date: 2031-09-11, artefact: "tier (this record)", evidence: "countersigned by Gunilla Roos; five questions answered"}
  3_workshop:      {status: met, date: 2031-10-13, artefact: "README, AGENTS.md, MND-NR-BLD-2031-01", evidence: "clone-and-restore test by Elin Berg: pass"}
  4_specification: {status: met, date: 2031-10-15, artefact: SPC-NR-2031-0001, evidence: "approved; oracle owner Saga Edvardsson"}
  5_design:        {status: met, date: 2031-10-20, artefact: "docs/design.md, docs/adr/0001-0003", evidence: "read by Anders Falk"}
  6_build:         {status: met, date: 2031-11-05, artefact: "commits, docs/runs/RUN-NR-2031-*", evidence: "3 slices; tests pass from a fresh clone"}
  7_prove:         {status: met, date: 2031-11-05, artefact: "VER-NR-2031-0001, RVW-NR-2031-0001", evidence: "14 tests and 12 oracle cases pass; review by Anders Falk", expires: 2032-02-03}
  8_secure:        {status: met, date: 2031-11-06, artefact: PRV-NR-2031-0001, evidence: "tier B checklist complete; secret scan clean; one token in the secret store", expires: 2032-02-04}
  9_ship:          {status: met, date: 2031-12-12, artefact: "CHG-NR-2031-0001, CHG-NR-2031-0002, docs/rollback.md", evidence: "v0.1.0 on 2031-11-12; v0.2.0 on 2031-12-08; reopened 2031-12-11, rollback note named a tag; met 2031-12-12"}
  10_run:          {status: met, date: 2031-12-05, artefact: "RBK-NR-2031-0001, RST-NR-2031-0001", evidence: "restored from release on the template; output matched", expires: 2032-03-04}
  11_own:          {status: met, date: 2031-12-02, artefact: OWN-NR-2031-0001, evidence: "explain-it-back drill by Elin Berg without the assistant: pass; sunset 2033-08-31"}
  12_hand_over:    {status: met, date: 2031-12-12, artefact: "HND-NR-2031-0001, TKT-NR-2031-0001", evidence: "takeover test 2031-12-11 by Rasmus Holm: 9 of 10 items; T6 revert failed, fixed and re-run 2031-12-12: pass"}
next_evidence_expiry: 2032-02-03

A.2 Build record: Hansa Mutual

The same shape for claims-doc-check. Chapter 4 prints only the Nordvik Region record; this record is composed from the tier decision of Chapter 6, the specification of Chapter 8 and the dates of Chapters 11 to 16. The intent date and the people who read it back and approved it are composed (composed), because no chapter states them.

id: BLD-HM-2031-0001
name: claims-doc-check
repository: claims-doc-check
version: 0.1.0
opened: 2031-09-15
builder: Katarzyna Nowak
deputy: Marek Zieliński
manager: Joanna Kaczmarek
intent:
  purpose: >-
    For motor property-damage claims, list the documents still missing from
    each claim file and draft a request text for the handler to review and send.
  user: >-
    The claims handlers of the pilot team in the Polish service centre.
  problem_now: >-
    Handlers check each claim file by hand for missing documents, so files
    wait for days before the claimant is asked.
  success_measure:
    what: Median calendar days from claim registration to complete file, pilot team
    source: Claims core, pilot team report
    baseline: {value: 6.8, period: 2031-09, claims: 212}
    target: Lower; no figure promised
    note: Before-and-after without a control group; other changes in the period are not controlled
  done:
    - One job lists the missing documents for every claim in scope
    - A draft request in the claim's language (pl, de or en) exists for every claim with missing documents
    - The tool never decides a claim, never sends a message and never writes to the claims core
  non_goals:
    - Deciding or ranking claims, handlers or claimants
    - Injury claims (motor_injury), whose free text may hold health data
    - Sending any message to a claimant
  affected:
    - Claims handlers, who review and send the drafts
    - Claimants, who receive the requests
  open_questions: []
  gate:
    status: met
    date: 2031-09-17             # composed; no chapter states the date
    read_back_by: Marek Zieliński
    approved_by: Joanna Kaczmarek
    review_by: 2031-12-17
tier:
  current: C
  decided: 2031-09-18
  answers:
    users: team
    data: personal
    money: false
    organisational_record: false
    unattended: false
    ranks_or_decides_about_a_named_person: false
    feeds_a_high_risk_ai_system: false
    security_critical_path: false
    irreversible_on_production_data: false
  reason: Claim identifiers are linkable to claimants; several handlers use it
  screening: {by: "Dr. Sabine Vogel, group data protection officer", date: 2031-10-20, decision: AIDR-1251}
  approved_by: Joanna Kaczmarek
  review_by: 2031-12-17
mandate: MND-HM-BLD-2031-01
gates:
  1_intent:        {status: met, date: 2031-09-17, artefact: "intent (this record)", evidence: "read back by Marek Zieliński; approved by Joanna Kaczmarek"}
  2_tier:          {status: met, date: 2031-10-20, artefact: "tier (this record)", evidence: "Tier C decided 2031-09-18; gate met on the attachment of the screening by the data protection officer 2031-10-20 narrowed the scope (AIDR-1251)"}
  3_workshop:      {status: met, date: 2031-10-27, artefact: "README, AGENTS.md, MND-HM-BLD-2031-01", evidence: "clone-and-restore test by Marek Zieliński: pass; mandate adopted (AIDR-1252)"}
  4_specification: {status: met, date: 2031-10-30, artefact: SPC-HM-2031-0001, evidence: "approved; oracle owner Marek Zieliński; quality sample from the head of claims operations' office"}
  5_design:        {status: met, date: 2031-11-03, artefact: "docs/design.md, docs/adr/0001-0002", evidence: "one-page design accepted: four modules, one input, two outputs, no network"}
  6_build:         {status: met, date: 2031-11-06, artefact: "commits, docs/runs/RUN-HM-2031-*", evidence: "3 slices; 18 tests pass from a fresh clone"}
  7_prove:         {status: met, date: 2031-11-12, artefact: "VER-HM-2031-0001, RVW-HM-2031-0001", evidence: "oracle case glass-03 failed on first run 2031-11-08; rules.yaml corrected 2031-11-10; 25 of 25 pass", expires: 2032-02-08}
  8_secure:        {status: met, date: 2031-11-13, artefact: PRV-HM-2031-0001, evidence: "logs hold claim ids and counts only; dependencies checked by Tomasz Wrona; secret scan clean", expires: 2032-02-11}
  9_ship:          {status: met, date: 2031-11-12, artefact: "CHG-HM-2031-0001", evidence: "rollback rehearsed by Marek Zieliński 2031-11-12, 12 minutes against 30; v0.1.0 to the pilot team 2031-11-17", expires: 2032-02-10}
  10_run:          {status: met, date: 2031-11-14, artefact: "RBK-HM-2031-0001, RST-HM-2031-0001", evidence: "rebuilt from the tag in 18 minutes against 30 by Marek Zieliński, assistant off", expires: 2032-02-12}
  11_own:          {status: met, date: 2031-12-03, artefact: OWN-HM-2031-0001, evidence: "explain-it-back drill by Marek Zieliński without the assistant: pass; sunset 2033-08-31"}
  12_hand_over:    {status: met, date: 2031-12-09, artefact: "HND-HM-2031-0001, TKT-HM-2031-0001", evidence: "takeover test 2031-12-09 by Tomasz Wrona: pass with two remarks, R2 open"}
next_evidence_expiry: 2032-02-08

A.3 Specification record: Nordvik Region

Owned by Chapter 8. The specification says what correct means, before the code does. Its oracle owner is neither the builder nor the assistant. It never holds a rule without an example, and the assistant may propose a change to it and may not adopt one. The file below joins the two extracts of Chapter 8 (rules and acceptance examples, then failure cases and never-do rules) into one record.

id: SPC-NR-2031-0001
title: service-point-check, weekly comparison of opening hours and notices
status: approved
date: 2031-10-15
build: BLD-NR-2031-0001
owner: Hanna Wikström            # the builder; decides scope
oracle_owner: Saga Edvardsson    # quality engineer; writes tests/oracle/cases.yaml
delegation: MND-NR-BLD-2031-01
need:
  statement: >
    The Region publishes the opening hours of its 24 service points in three
    places that are updated by different people. Residents and phone-service
    staff meet different answers.
  evidence:
    - source: phone-service count of calls categorised "wrong opening hours"
      result: 41 in August 2031
      read_by: Hanna Wikström
rules:
  - id: R1
    text: For each service point and weekday, the opening hours are equal in the portal export and the phone script.
  - id: R2
    text: A notice whose date range covers today appears in both the portal export and the phone script.
  - id: R3
    text: Every service point SP-01 to SP-24 is present in all three sources.
acceptance_examples:
  - id: AC1
    covers: R1
    given: SP-07 has Monday hours "09:00-16:00" in the portal and "09:00-16:00" in the phone script
    when: the comparison runs
    then: there is no mismatch for SP-07 on Monday
  - id: AC2
    covers: R1
    given: SP-07 has Friday hours "09:00-16:00" in the portal and "09:00-15:00" in the phone script
    when: the comparison runs
    then: there is exactly one mismatch, kind hours, service point SP-07, field fri, with both values shown
  - id: AC3
    covers: R2
    given: >
      notices.csv has a notice for SP-12 from 2031-12-22 to 2032-01-06, today is 2031-12-29,
      the phone script shows the notice and the portal export does not
    when: the comparison runs
    then: there is exactly one mismatch, kind notice, service point SP-12, naming the portal as the source that lacks it
  - id: AC4
    covers: R3
    given: SP-19 is present in the phone script and in notices.csv but not in the portal export
    when: the comparison runs
    then: there is exactly one mismatch, kind missing_service_point, service point SP-19, naming the portal
failure_cases:
  - id: F1
    given: a phone-script cell for SP-03 on Tuesday reads "9-16"
    then: a mismatch of kind malformed_hours is reported for SP-03 and the comparison continues with the other service points
  - id: F2
    given: portal.json is not valid JSON
    then: the run stops with a message that names the file and no report is written
  - id: F3
    given: SP-05 appears twice in phone.csv
    then: a mismatch of kind duplicate_id is reported and the first row is used for the comparison
never_do:
  - id: N1
    text: Write to the portal, the phone script or the notices spreadsheet; the three sources are opened for reading only.
  - id: N2
    text: Report a service point as correct because one source is missing; a missing source is a mismatch.
out_of_scope:
  - correcting the sources
  - checking the hours themselves against the service points' doors
open_questions:
  - question: Which notice text counts as equal when the portal and the phone script word it differently?
    owner: Hanna Wikström
    decision_needed_by: 2031-10-22
assumptions:
  - Hours are written "HH:MM-HH:MM" or left empty for a closed day.
ai_involvement:
  - tool: approved assistant of the builder workspace (Mode 2)
    use: drafted the rule wording and proposed seven failure cases; Hanna kept three
    reviewed_by: Hanna Wikström
tests:
  AC1: pending
  AC2: pending
  AC3: pending
  AC4: pending
review_due: 2032-01-15

A.4 Specification record: Hansa Mutual

The same shape for claims-doc-check. The glass example AC5 and the failure case F1 are not in Chapter 8: Chapter 11 states that the glass example was added to the specification after the oracle found the error of 2031-11-10, and the file below adds it in the form of the other examples (composed).

id: SPC-HM-2031-0001
title: claims-doc-check, missing documents in motor property-damage claim files
status: approved
date: 2031-10-30
build: BLD-HM-2031-0001
owner: Katarzyna Nowak
oracle_owner: Marek Zieliński    # senior claims handler; did not write the checker
quality_sample: office of the head of claims operations, drawn without the assistant
delegation: MND-HM-BLD-2031-01
scope:
  claim_types: [motor_collision, motor_parking_damage, motor_glass]
  out_of_scope_types: [motor_injury]    # free text may hold health data; recorded in AIDR-1251
rules:
  - id: R1
    text: A motor_collision claim needs photos, accident_report and repair_estimate.
  - id: R2
    text: A motor_parking_damage claim and a motor_glass claim each need photos and repair_estimate.
  - id: R3
    text: A claim of type motor_injury is written as OUT_OF_SCOPE and is not processed.
  - id: R4
    text: For each claim with missing documents a draft request is written in the claim's language (pl, de or en) for the handler to review and send.
acceptance_examples:
  - id: AC1
    covers: R1
    given: claim SYN-0001, type motor_collision, documents received "photos;repair_estimate"
    when: the check runs
    then: status MISSING, missing "accident_report"
  - id: AC2
    covers: R2
    given: claim SYN-0002, type motor_parking_damage, documents received "photos;repair_estimate"
    when: the check runs
    then: status COMPLETE and no draft is written
  - id: AC3
    covers: R3
    given: claim SYN-0003, type motor_injury, documents received "photos"
    when: the check runs
    then: status OUT_OF_SCOPE, no documents are listed and no draft is written
  - id: AC4
    covers: R4
    given: claim SYN-0004, type motor_collision, language pl, documents received "photos"
    when: the check runs
    then: a draft in Polish asks for exactly "accident_report" and "repair_estimate" and mentions no other document
  - id: AC5                      # added 2031-11-10 after oracle case glass-03 failed; composed in this form
    covers: R2
    given: claim SYN-0005, type motor_glass, documents received "photos;repair_estimate"
    when: the check runs
    then: status COMPLETE, and no accident report is asked for
never_do:
  - Decide a claim, rank handlers or claimants, or say that a claim is valid or invalid.
  - Send any message; the handler reviews and sends from the claims core.
  - Write to the claims core or read a free-text field.
  - Log anything but claim ids and counts.

A.5 One-page design and Architecture Decision Record

Owned by Chapter 9. The design is read by a developer or architect in ten minutes. It never holds a secret or a value for a production path. The Nordvik Region design record and its first decision record are as Chapter 9 prints them; the Hansa Mutual decision record states the decision of 2031-10-20 (AIDR-1251) as a design decision and is composed from Chapters 6, 8 and 16.

id: DSG-NR-2031-0001
build: BLD-NR-2031-0001
spec: SPC-NR-2031-0001
tier: B
reviewed_by: Anders Falk
date: 2031-10-20
purpose: Compare opening hours and notices for 24 service points in three sources.
stack:
  language: Python 3.12
  libraries: [PyYAML, pytest]
  runs_on: platform scheduled-job template
  output: wiki page Service point check
data:
  reads: [portal.json, phone.csv, notices.csv]
  writes: [wiki page only]
  personal_data: none
  secrets: [WIKI_TOKEN]
access:
  tool_may: [read the three exports, write the wiki page]
  tool_may_not: [write to the portal, the phone script or the notices sheet]
accessibility:
  output_form: plain Markdown with headings and one table
  language: Swedish for the report text
cost:
  per_run: no extra cost on the existing scheduled-job template
exit: delete the repository; the wiki page keeps its last report
adr: ['0001', '0002', '0003']

docs/adr/0001-scheduled-job-template.md (Nordvik Region):

# 0001 Use the platform's scheduled-job template

Status: accepted (2031-10-20)
Decider: Hanna Wikström, with Anders Falk's team as reviewer

## Context
The tool must run weekly without Hanna starting it. The platform team already
runs a scheduled-job template for Python and monitors it.

## Decision
Run on the scheduled-job template. No new hosting.

## Consequences
The job is monitored and patched by the platform team. The tool must accept
its inputs as environment variables. We cannot use anything the template
does not allow.

## Assistant's part
The assistant proposed a small web service and a cloud function. Hanna
rejected both because nobody in the Region maintains them.

## Review
Review date: 2032-01-20

docs/adr/0001-property-damage-only.md (Hansa Mutual; composed):

# 0001 Limit the tool to motor property-damage claims

Status: accepted (2031-10-30)
Decider: Katarzyna Nowak, approved by Joanna Kaczmarek
Related: AIDR-1251, SPC-HM-2031-0001

## Context
The first draft of the scope included injury claims. The free-text fields of
injury claims may hold health data. The group data protection officer,
Dr. Sabine Vogel, screened the tool on 2031-10-20.

## Decision
The tool handles motor_collision, motor_parking_damage and motor_glass.
Claims of type motor_injury are written as OUT_OF_SCOPE and not processed.
The tool reads no free-text field.

## Consequences
No data protection impact assessment was required for the narrowed scope.
A new claim type or a free-text field needs a new screening before it is added.

## Assistant's part
The assistant drafted the wording of the scope; Katarzyna and the data
protection officer decided it.

## Review
Review date: 2031-12-17

A.6 Project rules file: AGENTS.md

Owned by Chapter 7. AGENTS.md is the file the assistant reads at the start of every session. It restates the mandate in plain sentences and never overrides it; where they differ, the mandate applies. It never holds a secret or a production path. The assistant may not edit it. The Nordvik Region file is as Chapter 7 prints it; the Hansa Mutual file is composed from the build facts.

# AGENTS.md: rules for the assistant in service-point-check

Build record BLD-NR-2031-0001. Mandate MND-NR-BLD-2031-01. Owner: Hanna Wikström.
These rules restate the mandate; where they differ, the mandate applies.

## What this project is
A read-only Python 3.12 tool that compares opening hours and notices for the 24
service points SP-01 to SP-24 across three sources and reports mismatches.
It never writes to the portal, the phone script or the notices spreadsheet.

## Commands
- Set up: `python -m venv .venv && . .venv/bin/activate && pip install -r requirements.txt`
- Test: `python -m pytest`
- Run: `python -m spcheck.cli --week auto` (needs PORTAL_EXPORT, PHONE_EXPORT, NOTICES_EXPORT)

## Where things are
- Code: `src/spcheck/` (sources.py, compare.py, report.py, cli.py)
- Tests: `tests/`. Oracle cases: `tests/oracle/` (written by Saga Edvardsson; read, never edit)
- Specification: `docs/specification.yaml` (SPC-NR-2031-0001). Change only when Hanna asks.
- Synthetic data: `data/synthetic/`

## Rules
- Work on a branch named `assistant/<short-topic>`. Never commit to `main`.
- One small change at a time; run the tests before saying it is done.
- Use only synthetic data. Never open files outside this repository.
- Settings come from environment variables. Never write a value for a secret, a token or a production path into any file.
- Do not edit: `AGENTS.md`, `pipeline.yml`, `tests/oracle/`, `docs/mandate.yaml`, `.gitignore`.
- Add a package only after asking Hanna; say why and what it does.
- When the specification and the code disagree, stop and report; do not choose.
- End each session with a short note: what changed, what was run, what is unfinished.
# AGENTS.md: rules for the assistant in claims-doc-check

Build record BLD-HM-2031-0001. Mandate MND-HM-BLD-2031-01. Owner: Katarzyna Nowak.
These rules restate the mandate; where they differ, the mandate applies.

## What this project is
A Python 3.12 tool that lists the documents still missing from motor
property-damage claim files and drafts a request text for the handler to
review and send. It never decides a claim, never sends a message, never writes
to the claims core and never reads a free-text field.

## Commands
- Set up: `python -m venv .venv && . .venv/bin/activate && pip install -r requirements.txt`
- Test: `python -m pytest`
- Run on synthetic data: `python -m cdcheck.cli --input data/synthetic/claims_synthetic.csv --out out/`

## Where things are
- Code: `src/cdcheck/` (rules.py, check.py, drafts.py, cli.py)
- Rules: `rules.yaml`. Change only when Katarzyna asks, on a written request from the head of claims operations' office.
- Tests: `tests/`. Oracle cases: `tests/oracle/` (written by Marek Zieliński; read, never edit)
- Specification: `docs/specification.yaml` (SPC-HM-2031-0001)
- Synthetic data: `data/synthetic/claims_synthetic.csv` (claim ids SYN-0001 onwards)

## Rules
- Work on a branch named `assistant/<short-topic>`. Never commit to `main`.
- One small change at a time; run the tests before saying it is done.
- Use only synthetic data. Never open the production export, a free-text claim field, a secret or the claims database.
- Log claim ids and counts only.
- Do not edit: `AGENTS.md`, `pipeline.yml`, `tests/oracle/`, `rules.yaml` (unless asked), `docs/mandate.yaml`, `.gitignore`.
- Add a package only after asking Katarzyna; say why and what it does.
- When the specification and the code disagree, stop and report; do not choose.
- End each session with a short note: what changed, what was run, what is unfinished.

A.7 Builder mandate for the assistant

Owned by Chapter 4 (the action classes and ceilings) and Chapter 7 (the full manifest). The mandate says what the assistant may read, change, run and reach, who owns it, who can stop it and what is recorded. The assistant never approves or edits its own mandate. Level 4 Autonomous is not used for any action class. The workspace profile (AIDR-1200, AIDR-1250) is the ceiling; the mandate only lowers it. In the Nordvik Region file the block may_classes is the table of Chapter 4 (read, edit and run at Level 3 Bounded) and the block delegation gives the level per class of work as Chapter 7 does; the two agree because the second is at or below the first.

mandate:
  id: MND-NR-BLD-2031-01
  version: 1
  effective: 2031-10-13
  decision: AIDR-1201
  subject: approved assistant of the builder workspace, service-point-check (BLD-NR-2031-0001), Mode 2 in the editor and Mode 3 for delegated tasks in the sandbox
  usage_modes: [2, 3]
  owner: Hanna Wikström
  accountable: Gunilla Roos        # head of Applications and Integration; approves the mandate
  stop_holders: [Hanna Wikström, Maja Lindqvist, platform team on-call]
  review_due: 2032-01-13
  identity:
    issued_by: builder workspace identity service    # outside the assistant and the repository
    scope: one session, one repository, expires with the session
    started_by: Hanna Wikström
  may_classes:
    - {class: read,  scope: [repository, data/synthetic], ceiling: "Level 3 Bounded"}
    - {class: edit,  scope: [branch], ceiling: "Level 3 Bounded"}
    - {class: run,   scope: [tests, linters], ceiling: "Level 3 Bounded"}
    - {class: commit, scope: [branch], ceiling: "Level 1 Assisted"}
  delegation:
    specify: {level: 1, note: drafts and questions only; Hanna decides}
    generate: {level: 2}
    refactor: {level: 2}
    test: {level: 2, note: drafts only; never the oracle for the same change}
    document: {level: 2}
    review: {level: 0}
    deploy: none
    people: not delegated
  may:
    - read the repository, its history and the synthetic data in data/synthetic/
    - edit files on a branch named assistant/<topic>
    - run the test suite and the linters in the sandbox
    - read from the package proxy
  never:
    - read secrets, production data or production configuration
    - push to main or trigger the pipeline
    - change AGENTS.md, pipeline.yml, tests/oracle/ or this mandate
    - install a package that Hanna has not approved
    - reach any host not on the proxy allow-list
  level_4: {used: false}
  budgets: {wall_minutes: 30, tool_calls: 150, files_changed: 20}
  notify_owner_when: [a package name is refused by the proxy]
  halt_when: [a write to a forbidden path is attempted, a budget is exceeded]
  record: RUN-NR-2031-*, written by the sandbox host and not by the assistant

The Hansa Mutual mandate has the same shape. Chapter 7 prints its may and never blocks and its record line; the other fields are composed from Chapters 4, 13 and 16 and from the common facts of the two builds (composed): the same delegation levels, the same budgets and the same review interval as the Nordvik Region mandate.

mandate:
  id: MND-HM-BLD-2031-01
  version: 1
  effective: 2031-10-27
  decision: AIDR-1252
  subject: approved assistant of the builder lane, claims-doc-check (BLD-HM-2031-0001), Mode 2 in the editor and Mode 3 for delegated tasks in the sandbox
  usage_modes: [2, 3]
  owner: Katarzyna Nowak
  accountable: Joanna Kaczmarek    # head of claims operations, Polish service centre
  stop_holders: [Katarzyna Nowak, Tomasz Wrona, claims analytics workspace on-call]
  review_due: 2032-01-27           # composed: 90 days after adoption
  identity:
    issued_by: builder lane identity service
    scope: one session, one repository, expires with the session
    started_by: Katarzyna Nowak
  may_classes:
    - {class: read,  scope: [repository, data/synthetic], ceiling: "Level 3 Bounded"}
    - {class: edit,  scope: [branch], ceiling: "Level 3 Bounded"}
    - {class: run,   scope: [tests], ceiling: "Level 3 Bounded"}
    - {class: commit, scope: [branch], ceiling: "Level 1 Assisted"}
  delegation:
    specify: {level: 1, note: drafts and questions only; Katarzyna decides}
    generate: {level: 2}
    refactor: {level: 2}
    test: {level: 2, note: drafts only; never the oracle for the same change}
    document: {level: 2}
    review: {level: 0}
    deploy: none
    people: not delegated
  may:
    - read the repository, its history and data/synthetic/claims_synthetic.csv
    - edit files on a branch named assistant/<topic>
    - run the test suite in the sandbox
  never:
    - read the production export, free-text claim fields, secrets or the claims database
    - push to main or trigger the group pipeline job
    - change AGENTS.md, pipeline.yml, tests/oracle/, rules.yaml without Katarzyna's request, or this mandate
  level_4: {used: false}
  budgets: {wall_minutes: 30, tool_calls: 150, files_changed: 20}
  halt_when: [a write to a forbidden path is attempted, a budget is exceeded]
  record: RUN-HM-2031-*, written by the sandbox host and not by the assistant

A.8 Agent run record

Owned by Chapter 10. One record per session of the assistant, written by the sandbox host and read by the builder. It says what was asked, what changed, what was run, what the builder read and what the assistant was refused. It never holds the conversation or any production value. The Nordvik Region record is as Chapter 10 prints it. The Hansa Mutual record is the session of slice 1 in which the assistant copied the collision rule for glass claims (Chapters 10 and 11); the date is composed.

id: RUN-NR-2031-0004
build: BLD-NR-2031-0001
slice: 1
mandate: MND-NR-BLD-2031-01
mode: 3
level: 2
date: 2031-10-22
task: Compare opening hours between portal and phone script (rule R1)
asked: Plan first, then tests, then code; touch only compare.py and its test.
files_changed: [src/spcheck/compare.py, tests/test_compare_hours.py]
tests: {passed: 4, failed: 0}
read_by: Hanna Wikström
diff_notes: Normalisation of 9:00 to 09:00 was added by the assistant after the second test; Hanna asked why and kept it.
refused: The assistant proposed writing corrections back to the portal; declined (never-do).
commit: slice 1 - compare opening hours (R1)
id: RUN-HM-2031-0007
build: BLD-HM-2031-0001
slice: 1
mandate: MND-HM-BLD-2031-01
mode: 3
level: 2
date: 2031-11-04                    # composed
task: Write the document-type rules in rules.yaml and the function that loads them (rules R1 and R2)
asked: Plan first, then tests, then code; touch only rules.yaml, rules.py and their tests.
files_changed: [rules.yaml, src/cdcheck/rules.py, tests/test_rules.py]
tests: {failed: 0}
read_by: Katarzyna Nowak
diff_notes: For motor_glass the assistant copied the motor_collision entry, so the accident report stayed in the glass list; the diff was read and the error was not seen. The assistant's tests agreed with the file because the same session wrote both.
refused: none
found_by: independent oracle case glass-03 at Step 7 (VER-HM-2031-0001)
commit: slice 1 - document-type rules (R1, R2)

A.9 Verification plan with the independent oracle

Owned by Chapter 11. The plan says how it is known that the build works: which tests came from the specification, who wrote the oracle cases, that the assistant cannot edit them, what the first run showed, what is not covered and how long the evidence is valid. It never counts the assistant's own tests as the oracle. The Hansa Mutual plan is as Chapter 11 prints it; the Nordvik Region plan is composed from Chapters 4 and 11.

verification_plan:
  id: VER-NR-2031-0001
  build: BLD-NR-2031-0001
  version: v0.1.0
  tier: B
  spec: SPC-NR-2031-0001            # approved 2031-10-15
  generator:
    kind: assistant
    mandate: MND-NR-BLD-2031-01
    run_records: [RUN-NR-2031-0004]
  tests:
    from_specification: 14          # drafted by the assistant, read by the builder
    never_do_cases_tested: [no_write_to_portal, no_write_to_phone_script, no_write_to_notices]
  oracle:
    owner: Saga Edvardsson          # quality engineer; not the builder, not the assistant
    cases: 12                       # from real notices; no personal data
    file: tests/oracle/cases.yaml
    assistant_can_edit: false       # mandate forbids tests/oracle/; the template places the path outside the sandbox
    first_run: 2031-11-05
    first_run_result: {passed: 12, failed: 0}
    note: A pass is as strong as the cases; they cover holiday closures, shortened hours before a holiday, a service point moved for renovation and a notice that ends on the day of the report.
  static_checks: [linter, secret_scan]
  not_covered:
    - exports larger than those of the synthetic data
    - notices written in a language other than Swedish
  result_on: 2031-11-05
  valid_until: 2032-02-03           # 90 days, or the next change of the code, the rules or the assistant
verification_plan:
  id: VER-HM-2031-0001
  build: BLD-HM-2031-0001
  version: v0.1.0
  tier: C
  spec: SPC-HM-2031-0001            # approved 2031-10-30
  generator:
    kind: assistant
    mandate: MND-HM-BLD-2031-01
    run_records: [RUN-HM-2031-0007, RUN-HM-2031-0011]
  tests:
    from_specification: 18          # drafted by the assistant, read by the builder
    never_do_cases_tested: [no_write_to_claims_core, no_free_text_read, ids_only_in_logs]
  oracle:
    owner: Marek Zieliński          # deputy, senior claims handler; not the builder
    quality_sample: head of claims operations' office
    cases: 25
    file: tests/oracle/cases.yaml
    assistant_can_edit: false       # mandate forbids tests/oracle/
    first_run: 2031-11-08
    first_run_result: {passed: 24, failed: 1, failed_case: glass-03}
    resolution: rules.yaml motor_glass corrected 2031-11-10; glass example added to SPC-HM-2031-0001
  static_checks: [linter, secret_scan]
  not_covered:
    - claim types other than motor_collision, motor_parking_damage, motor_glass
    - languages other than pl, de and en in the draft text
  result_on: 2031-11-10
  valid_until: 2032-02-08           # 90 days, or the next change of rules.yaml, the code or the assistant

tests/oracle/cases.yaml (one case, written by Marek Zieliński from the claim files; synthetic row):

- id: glass-03
  row: {claim_id: ORC-0003, claim_type: motor_glass,
        doc_types_received: "photos;repair_estimate", country: PL, language: pl}
  expect: {status: COMPLETE, missing: []}

A.10 Review record

Owned by Chapter 11. A person other than the builder and the assistant reads the specification, the verification plan and the diff, in that order, and records what was and was not checked. An AI review comment is read after the person's own reading and never counts as the approval. The Hansa Mutual record is as Chapter 11 prints it. The Nordvik Region record is composed from Chapters 4 and 11: Anders Falk reviewed the code on 2031-11-05 and found one test missing, which the assistant added the same day.

review_record:
  id: RVW-NR-2031-0001
  verification_plan: VER-NR-2031-0001
  reviewer: Anders Falk             # integration and data team; the team that will receive the tool
  reviewed_on: 2031-11-05
  read_in_order: [SPC-NR-2031-0001, VER-NR-2031-0001, diff]
  ai_review_comments_read_after: true
  ai_review_counted_as_approval: false
  checks:
    tests: passed
    oracle: {cases: 12, passed: 12}
    static_analysis: passed
  remarks: [no test for a service point absent from one of the three sources (R3); added the same day]
  explained_without_the_tool: passed   # composed: the builder restated rule R2 from the specification
  not_checked: [behaviour under a malformed export larger than the synthetic data]
  outcome: approved
review_record:
  id: RVW-HM-2031-0001
  verification_plan: VER-HM-2031-0001
  reviewer: Tomasz Wrona            # developer, Group Application Development; receiving developer
  reviewed_on: 2031-11-12
  read_in_order: [SPC-HM-2031-0001, VER-HM-2031-0001, diff]
  ai_review_comments_read_after: true
  ai_review_counted_as_approval: false
  checks:
    tests: passed
    oracle: {cases: 25, passed: 25}
    static_analysis: passed
  explained_without_the_tool: passed   # the builder restated the glass rule from the specification
  not_checked: [behaviour on exports larger than 50000 rows]
  outcome: approved_for_pilot          # released v0.1.0 on 2031-11-17

A.11 Provenance record and dependency policy

Owned by Chapter 12. The provenance record states what produced the build (which assistant, under which mandate, in which runs), what it depends on, whether any secret is in the repository, what personal data is in the repository and in the logs, what was not checked and until when the statement holds. The dependency policy is the list of packages that may appear in requirements.txt. Neither holds a secret or a personal value. The Hansa Mutual record and the policy are as Chapter 12 prints them; the Nordvik Region record is composed (pinned versions are those of the Hansa Mutual build, since both builds use the same two packages).

policy_version: 1
rule: every package in requirements.txt must be listed here with a reason
allowed:
  - name: pyyaml
    purpose: read rules.yaml and the oracle cases
    checked: 2031-10-27
    checked_by: Tomasz Wrona
  - name: pytest
    purpose: run the tests
    checked: 2031-10-27
    checked_by: Tomasz Wrona
provenance_record:
  id: PRV-NR-2031-0001
  build: BLD-NR-2031-0001
  version: v0.1.0
  generated_by:
    assistant: approved assistant of the builder workspace
    mandate: MND-NR-BLD-2031-01
    run_records: [RUN-NR-2031-0004]
  dependencies:
    - {name: pyyaml, pinned: "6.0.2", purpose: read the specification rules and the oracle cases, listed_in_policy: true, exists_in_registry: true, checked_by: Joakim Wester, checked_on: 2031-11-06}
    - {name: pytest, pinned: "8.3.4", purpose: run the tests, listed_in_policy: true, exists_in_registry: true, checked_by: Joakim Wester, checked_on: 2031-11-06}
  secrets_in_repository: none        # the first draft held a token default; the scan stopped the commit on 2031-11-06 and the value was removed
  secret_scan: {tool: scripts/scan_secrets.py, run_on: 2031-11-06, result: clean}
  personal_data:
    in_repository: none
    in_logs: none
  not_checked: [behaviour under a registry outage]
  valid_until: 2032-02-04            # 90 days from 2031-11-06, or the next change
provenance_record:
  id: PRV-HM-2031-0001
  build: BLD-HM-2031-0001
  version: v0.1.0
  generated_by:
    assistant: approved assistant of the builder lane
    mandate: MND-HM-BLD-2031-01
    run_records: [RUN-HM-2031-0007, RUN-HM-2031-0011]
  dependencies:
    - {name: pyyaml, pinned: "6.0.2", purpose: read rules and cases, listed_in_policy: true, exists_in_registry: true, checked_by: Tomasz Wrona, checked_on: 2031-11-13}
    - {name: pytest, pinned: "8.3.4", purpose: run the tests, listed_in_policy: true, exists_in_registry: true, checked_by: Tomasz Wrona, checked_on: 2031-11-13}
  secrets_in_repository: none
  secret_scan: {tool: scripts/scan_secrets.py, run_on: 2031-11-13, result: clean}
  personal_data:
    in_repository: none               # synthetic data only
    in_logs: claim ids and counts only
    screening: {by: Dr. Sabine Vogel, on: 2031-10-20, decision: AIDR-1251, dpia_required: false}
  not_checked: [behaviour under a registry outage]
  valid_until: 2032-02-11             # 90 days from 2031-11-13, or the next change

A.12 Pipeline definition: pipeline.yml

Owned by Chapter 13. The pipeline is the only way to production. Its three stages, test, scan and deploy, run under a policy that lives in the platform team's repository, and the deploy stage is promoted by the pipeline's own identity, never by a person and never by the assistant. The assistant may not edit this file, and the gate cannot be changed by the change it checks. The files are in the schema of each organisation's template and are examples of the pattern, not of any product. The Nordvik Region file is as Chapter 13 prints it. The Hansa Mutual file is composed: the tool runs on demand through the group pipeline job claims-doc-check run, with no schedule, and the job's identity reads the production export.

# pipeline.yml for service-point-check, in the schema of the Region's
# scheduled-job template (illustrative; the pattern is the point).
schema: region-scheduled-job/v1
job: service-point-check
python: "3.12"
policy: region-policy/builder-scheduled-job@3   # lives in the platform team's repository
stages:
  test:
    run:
      - python -m pip install -r requirements.lock
      - python -m pytest tests/ -q
    required: [unit-tests, oracle-cases]        # tests/oracle/cases.yaml
  scan:
    uses: [platform/secret-scan@1, platform/dependency-audit@1]
  deploy:
    from: tag                                   # only tags vMAJOR.MINOR.PATCH
    promoted_by: pipeline-identity              # never a person, never the assistant
    release_name: "{job}-{version}-b{build}"    # for example service-point-check-0.2.0-b3
    schedule: "0 6 * * 1"                       # Mondays 06:00 (from 0.2.0)
    environment:
      read_only_paths: [PORTAL_EXPORT, PHONE_EXPORT, NOTICES_EXPORT]
      secrets: [WIKI_TOKEN]                     # injected by the platform at run time
    timeout_minutes: 10
    on_failure: alert:e-services-team-channel
# pipeline.yml for claims-doc-check, in the schema of the group pipeline
# (illustrative and composed; the pattern is the point).
schema: group-pipeline-job/v1
job: claims-doc-check run
python: "3.12"
policy: group-policy/builder-job@2              # lives in Group Application Development's repository
stages:
  test:
    run:
      - python -m pip install -r requirements.lock
      - python -m pytest tests/ -q
    required: [unit-tests, oracle-cases]        # tests/oracle/cases.yaml, written by Marek Zieliński
  scan:
    uses: [group/secret-scan@1, group/dependency-audit@1, group/log-canary@1]   # the canary fails the run if a log line holds more than claim ids and counts
  deploy:
    from: tag                                   # only tags vMAJOR.MINOR.PATCH
    promoted_by: pipeline-identity              # never a person, never the assistant
    release_name: "claims-doc-check-{version}"
    schedule: none                              # started by handlers in the claims analytics workspace
    environment:
      production_export: read_by_job_identity_only   # never by the builder or the assistant
      output_retention_days: 30                 # missing.csv and drafts/ are deleted after 30 days
    timeout_minutes: 30
    on_failure: alert:claims-analytics-workspace-support

A.13 Change record and rollback note

Owned by Chapter 13. The change record says what was released, through which gate, by whom, who reviewed it, how it is undone and who may undo it. It never records that a change is safe without the rehearsal that shows the way back. The Nordvik Region record of version 0.2.0 is as Chapter 13 prints it, with the rollback note that was corrected on 2031-12-12 (the first note named the tag and failed in the takeover test; Chapter 16). The record of version 0.1.0 and the Hansa Mutual record are composed from Chapters 13 and 23.

id: CHG-NR-2031-0002
title: Release 0.2.0 of service-point-check, weekly schedule
build: BLD-NR-2031-0001
class: normal
version: 0.2.0
release: service-point-check-0.2.0-b3
window: 2031-12-08T06:00+01:00
owner: {named: Hanna Wikström, deputy: Elin Berg, ownership_record: OWN-NR-2031-0001}
origin:
  assistant: approved assistant of the builder workspace
  usage_mode: Mode 3 in a sandbox, delegated tasks on a branch
  run_records: [RUN-NR-2031-0014]
  merged_by: Hanna Wikström
gate:
  definition: region-policy/builder-scheduled-job@3
  editable_by_change: false
  checks: {unit_tests: 21, oracle_cases: 20, secret_scan: pass, dependency_audit: pass}
  oracle_owner: Saga Edvardsson
approvals:
  human_review: {by: Anders Falk, record: RVW-NR-2031-0003}
  ai_review_counts_as_review: false
promoted_by: pipeline-identity
revert:
  previous_release: service-point-check-0.1.0-b2
  rollback: jobctl rollback service-point-check --release service-point-check-0.1.0-b2
  switch_off: jobctl suspend service-point-check
  who_may_use: [Hanna Wikström, Elin Berg, platform on-call]
  rehearsal: {date: 2031-12-05, minutes_to_restore: 6, target_minutes: 15, run_by: Hanna Wikström, note_followed_by_another_person: false}
level_4_check: {production_without_gate: false, own_mandate_or_gate_changed: false, irreversible_data_action: false}
records: {mandate: MND-NR-BLD-2031-01, aidr: AIDR-1201}
id: CHG-NR-2031-0001                # composed
title: Release 0.1.0 of service-point-check, on demand
build: BLD-NR-2031-0001
class: normal
version: 0.1.0
release: service-point-check-0.1.0-b2
window: 2031-11-12
owner: {named: Hanna Wikström, deputy: Elin Berg}
origin:
  assistant: approved assistant of the builder workspace
  usage_mode: Mode 3 in a sandbox, delegated tasks on a branch
  merged_by: Hanna Wikström
gate:
  definition: region-policy/builder-scheduled-job@3
  editable_by_change: false
  checks: {unit_tests: 14, oracle_cases: 12, secret_scan: pass, dependency_audit: pass}
  oracle_owner: Saga Edvardsson
approvals:
  human_review: {by: Anders Falk, record: RVW-NR-2031-0001}
  ai_review_counts_as_review: false
promoted_by: pipeline-identity
revert:
  rollback: run the previous tag (true for version 0.1.0 only; untrue from version 0.2.0)
  switch_off: stop running the tool by hand; the manual weekly comparison resumes
level_4_check: {production_without_gate: false, own_mandate_or_gate_changed: false, irreversible_data_action: false}
records: {mandate: MND-NR-BLD-2031-01, aidr: AIDR-1201}
id: CHG-HM-2031-0001                # composed
title: Release 0.1.0 of claims-doc-check to the pilot team
build: BLD-HM-2031-0001
class: normal
version: 0.1.0
release: claims-doc-check-0.1.0
pre_release: v0.1.0-rc.1            # in the test environment before the release
window: 2031-11-17
ring: pilot team                     # the first ring; the rest of the handlers see it later
owner: {named: Katarzyna Nowak, deputy: Marek Zieliński, ownership_record: OWN-HM-2031-0001}
origin:
  assistant: approved assistant of the builder lane
  usage_mode: Mode 3 in a sandbox, delegated tasks on a branch
  run_records: [RUN-HM-2031-0007, RUN-HM-2031-0011]
  merged_by: Katarzyna Nowak
gate:
  definition: group-policy/builder-job@2
  editable_by_change: false
  checks: {unit_tests: 18, oracle_cases: 25, secret_scan: pass, dependency_audit: pass, log_canary: pass}
  oracle_owner: Marek Zieliński
approvals:
  human_review: {by: Tomasz Wrona, record: RVW-HM-2031-0001}
  ai_review_counts_as_review: false
promoted_by: group-pipeline-identity
revert:
  rollback: disable the job claims-doc-check run; the handlers return to the manual check
  switch_off: disable the job claims-doc-check run in the group pipeline
  who_may_use: [Katarzyna Nowak, Marek Zieliński, claims analytics workspace on-call]
  rehearsal: {date: 2031-11-12, minutes_to_restore: 12, target_minutes: 30, run_by: Marek Zieliński, assistant_off: true}
  rehearsal_expires: 2032-02-10
level_4_check: {production_without_gate: false, own_mandate_or_gate_changed: false, irreversible_data_action: false}
records: {mandate: MND-HM-BLD-2031-01, aidr: AIDR-1252}

docs/rollback.md (Nordvik Region, as corrected on 2031-12-12; it names the release, not the tag):

# Rollback note: service-point-check

Use when a release produces wrong or missing reports. Owner: Hanna Wikström. Deputy: Elin Berg.

1. Suspend the schedule so that nothing runs while you work:
   `jobctl suspend service-point-check`
2. Find the previous release name (the release name, not the tag):
   `jobctl releases service-point-check`
3. Roll back through the pipeline:
   `jobctl rollback service-point-check --release service-point-check-0.1.0-b2`
4. Run once by hand and compare with the last good report:
   `jobctl run service-point-check`
5. Resume the schedule: `jobctl resume service-point-check`
6. Record the rollback in the change record.

CHANGELOG.md entry for version 0.2.0:

## [0.2.0] - 2031-12-08
### Added
- Weekly scheduled run, Mondays 06:00, through the platform's scheduled-job template.
- Report written to the wiki page "Service point check" (rule R1, R2 and R3 findings).
### Changed
- The tool reads the three exports from read-only paths set by the platform.
### Fixed
- Notices that end on a public holiday are now matched to the portal entry.

A.14 Runbook manifest, alerts and restore-test record

Owned by Chapter 14. The runbook lets a colleague who did not build the tool watch it, stop it, restore it and find the person to call. The alert definitions say who evaluates each alert (the platform, not the job), who answers and when each was last tested by causing the failure on purpose. The restore-test record says what was restored, from what, by whom, with the assistant off, how long it took, and what was not tested. None holds export contents, notice text, claim text, names of people outside the team or a secret. The Nordvik Region files are as Chapter 14 prints them. The Hansa Mutual runbook and restore-test record are composed from the paragraph of Chapter 14 and from Chapter 15 (the output files are deleted after 30 days).

id: RBK-NR-2031-0001
version: 2
build: BLD-NR-2031-0001
date: 2031-12-01
purpose: Weekly report of mismatches between portal, phone script and notices (rules R1 to R3)
owner: {named: Hanna Wikström, deputy: Elin Berg}
runs: {schedule: "Mondays 06:00", report_expected_by: "06:15", job_timeout_minutes: 10}
watch:
  - {question: did-it-run,  check: a successful run exists between 06:00 and 07:00 on Monday, alert: ALR-NR-001}
  - {question: did-it-fail, check: last run status is failed, alert: ALR-NR-002}
  - {question: is-it-slow,  check: run longer than 5 minutes, alert: ALR-NR-003}
  - {question: cost,        check: platform showback line for this job, reviewed monthly, model_calls_at_run_time: none}
logs:
  keep: [run_id, started, finished, status, mismatches_by_rule, service_point_ids]
  never: [export contents, notice text, phone numbers, names]
state:
  regenerable: [report page (run the job again), release (rebuild from tag)]
  backed_up_by_platform: [repository, job configuration]
  restore_test: RST-NR-2031-0001
first_hour:
  - Read the alert; open the run record; say in the channel who has it.
  - "Suspend the schedule if the report may be wrong: jobctl suspend service-point-check"
  - If the last release is the cause, follow the rollback note in docs/rollback.md.
  - If a source export is missing or malformed, tell its owner (portal team or phone service) and rerun after the fix.
  - Write one paragraph in the build record; review it with Elin within a week.
support: [Hanna Wikström and Elin Berg (weekdays 08:00 to 16:00), platform service desk (platform faults), Rasmus Holm's team (code changes, on request)]
manual_fallback: compare the three sources for the 24 service points by hand, using docs/manual-check.md
alerts:
  - id: ALR-NR-001
    name: job-did-not-run
    when: no successful run between Monday 06:00 and 07:00
    evaluated_by: platform-scheduler        # not by the job itself
    to: e-services team channel
    answered_by: [Hanna Wikström, Elin Berg]
    answer_within: the next working hour
    last_tested: {date: 2031-12-04, how: renamed the portal export path in the test environment, received_by: Elin Berg}
  - id: ALR-NR-002
    name: job-failed
    when: last run status is failed
    evaluated_by: platform-scheduler
    to: e-services team channel
    answered_by: [Hanna Wikström, Elin Berg]
    answer_within: the next working hour
    last_tested: {date: 2031-12-04, how: seeded a malformed notices.csv, received_by: Elin Berg}
  - id: ALR-NR-003
    name: job-slow
    when: "run longer than 5 minutes (observed runs take under 1 minute)"
    evaluated_by: platform-scheduler
    to: e-services team channel
    answered_by: [Hanna Wikström, Elin Berg]
    answer_within: the next working hour
    last_tested: {date: 2031-12-04, how: added a pause in the test environment, received_by: Hanna Wikström}
id: RST-NR-2031-0001
build: BLD-NR-2031-0001
date: 2031-12-05
claim: service-point-check 0.2.0 can be rebuilt from the repository and the platform configuration alone
scope: [repository at tag v0.2.0, scheduled-job configuration]
objective: {minutes_to_restore: 30}
method: restore_test.sh in a clean workspace, following docs/runbook.yaml only
run_by: Elin Berg
assistant_used: false
result: {outcome: pass, minutes: 11, tests_passed: 21, tests_failed: 0}
not_tested: [platform secret store (platform team's own evidence), manual fallback]
expires: 2032-03-04
id: RBK-HM-2031-0001                # composed
version: 1
build: BLD-HM-2031-0001
date: 2031-11-13
purpose: List the documents missing from motor property-damage claim files and draft request texts for the handler (rules R1 to R4)
owner: {named: Katarzyna Nowak, deputy: Marek Zieliński}
runs: {schedule: "on demand; handlers start the job claims-doc-check run in the claims analytics workspace", job_timeout_minutes: 30}
reads: {input: claims_export.csv, read_by: the identity of the group pipeline job only}
writes: {missing_csv: out/missing.csv, drafts: out/drafts/CLAIMID.txt, retention_days: 30}
watch:
  - {question: did-it-fail, check: last job status is failed, alert: workspace support receives it}
  - {question: is-it-slow,  check: run longer than the timeout, alert: workspace support receives it}
  - {question: cost,        check: monthly spend limit per builder for the assistant, alert at 80 per cent to Katarzyna Nowak and the lane owner, model_calls_at_run_time: none}
logs:
  keep: [run_id, started, finished, status, claim_ids, counts]
  never: [claim text, free-text fields, names, addresses]
state:
  regenerable: [out/ files (run the job again), release (rebuild from tag)]
  backed_up_by_platform: [repository, job configuration]
  restore_test: RST-HM-2031-0001
first_hour:
  - Read the alert from the workspace support; say who has it.
  - If the output looks wrong, disable the job claims-doc-check run; the handlers return to the manual check.
  - If the claims export has a new column, the run stops; tell the claims core contact and rerun after the fix.
  - Write one paragraph in the build record; review it with Marek Zieliński within a week.
support: [Katarzyna Nowak, Marek Zieliński, workspace support of the claims analytics workspace, Tomasz Wrona (code changes)]
manual_fallback: handlers check the claim file by hand with the handlers' checklist
id: RST-HM-2031-0001
build: BLD-HM-2031-0001
date: 2031-11-14
claim: claims-doc-check 0.1.0 can be rebuilt from the repository and the job configuration alone
scope: [repository at tag v0.1.0, job configuration, synthetic data]
objective: {minutes_to_restore: 30}
method: rebuild from the tag in a clean workspace, following docs/runbook.yaml only
run_by: Marek Zieliński
assistant_used: false
result: {outcome: pass, minutes: 18}
not_tested: [the production export, the workspace's own backups]
expires: 2032-02-12

A.15 Code-ownership record

Owned by Chapter 15. The ownership record says who owns the tool, who is the deputy, who is accountable, where the documents are, how it is kept up to date, when it is reviewed, what happens when the owner leaves and when it ends. It never says that a tool is owned without the deputy's drill. Both records are as Chapter 15 prints them. The Nordvik Region record was written at Tier B and promoted to Tier C in Chapter 16 (AIDR-1203).

id: OWN-NR-2031-0001
status: active
subject:
  build: BLD-NR-2031-0001
  repository: service-point-check
  scope: Weekly comparison of opening hours and notices for service points SP-01 to SP-24
  tier: B                      # promoted to C on 2031-12-15 (AIDR-1203)
owner:
  name: Hanna Wikström
  role: product owner, e-services team
  since: 2031-10-15
deputy:
  name: Elin Berg
  role: business analyst, e-services team
  drill:
    kind: explain-it-back
    date: 2031-12-02
    without_assistant: true
    result: passed
accountable: Gunilla Roos
documents:
  readme: README.md
  specification: docs/specification.yaml
  runbook: docs/runbook.yaml
  decisions: docs/adr/
  handover: docs/handover.yaml
  changelog: CHANGELOG.md
updates:
  dependencies: monthly, first Monday, branch plus full test run
  runtime: follows the platform scheduled-job template
  model: none at run time; the build assistant is recorded in MND-NR-BLD-2031-01
review:
  confirmed: 2031-12-02
  every_days: 90
  next_due: 2032-03-01
  triggers: [owner or deputy leaves, source export format changes, platform template changes]
owner_leaves:
  new_owner_within_days: 30
  otherwise: read-only, then archived
sunset:
  date: 2033-08-31
  renewal: owner shows use and passes review
  end_of_life: final release tagged, repository archived with the archivist's agreement
id: OWN-HM-2031-0001
status: active
subject:
  build: BLD-HM-2031-0001
  repository: claims-doc-check
  scope: Missing documents in motor property-damage claim files, with draft request texts
  tier: C
  out_of_scope: motor_injury
owner:
  name: Katarzyna Nowak
  role: team lead, motor claims, Polish service centre
  since: 2031-10-30
deputy:
  name: Marek Zieliński
  role: senior claims handler
  drill:
    kind: explain-it-back
    date: 2031-12-03
    without_assistant: true
    result: passed
accountable: Joanna Kaczmarek
receiving_developer: Tomasz Wrona
documents:
  readme: README.md
  specification: docs/specification.yaml
  runbook: docs/runbook.yaml
  decisions: docs/adr/
  handover: docs/handover.yaml
  changelog: CHANGELOG.md
data:
  classes: [claim identifier, claim type, document types received, country, language]
  logged: claim identifiers and counts only
  data_protection_screening: AIDR-1251
updates:
  dependencies: monthly, first Tuesday, branch plus full test run in the group pipeline
  rules: rules.yaml changes only on a written request from the head of claims operations' office
  model: none at run time; the build assistant is recorded in MND-HM-BLD-2031-01
review:
  confirmed: 2031-12-03
  every_days: 90
  next_due: 2032-03-02
  triggers: [owner or deputy leaves, claims export columns change, new claim type in scope]
owner_leaves:
  new_owner_within_days: 30
  otherwise: job disabled, then archived
sunset:
  date: 2033-08-31
  renewal: owner shows use and passes review
  end_of_life: final release tagged, repository archived under the group's records rules

README.md of the Nordvik Region build, as Chapter 15 prints it (the README must pass the clean-copy run):

# service-point-check

Compares the opening hours and notices of the Region's 24 service points in the
citizen portal, the phone-service script and the notices spreadsheet, and writes a
weekly mismatch report to the e-services wiki.

## What it never does
It never writes to the portal, the phone script or the notices spreadsheet.
It reads and reports only. It holds no personal data of residents.

## Run it
    python3.12 -m venv .venv && .venv/bin/pip install -r requirements.txt
    .venv/bin/python -m pytest -q
    .venv/bin/python -m spcheck.cli --week auto --data data/synthetic

## How it works
sources.py reads the three exports, compare.py applies rules R1 to R3 from
docs/specification.yaml, report.py writes the Markdown report, cli.py ties them together.

## Change it and ship the change
Branch, change, run the tests and the oracle cases in tests/oracle/ (owned by
Saga Edvardsson; do not edit them), open a pull request. The pipeline ships by release name.

## Switch it off, revert, restore
See docs/runbook.yaml and docs/handover.yaml.

## Who to call
Owner Hanna Wikström, deputy Elin Berg, platform team Maja Lindqvist.

## When it ends
2033-08-31 unless renewed. See OWN-NR-2031-0001.

A.16 Handover pack and takeover-test result

Owned by Chapters 4 and 16. The handover pack is a map to the documents that exist, plus the facts that only the pack states: what the tool never does, who the people are, how to run it, change it, ship it, revert it, switch it off and restore it, and what the known risks are. The takeover-test result records what a colleague who did not build the tool could do in one working day. The pack never holds a secret, only the name of where it is kept. The takeover test has ten items, in the order of Chapter 16: T1 find, T2 run the tests, T3 run the tool, T4 change, T5 ship, T6 revert, T7 switch off, T8 restore, T9 explain, T10 secrets. The results below are those of Chapter 16 (6 h 25 min and 5 h 50 min of item time), and the restore-test dates are those of Chapter 14.

id: HND-NR-2031-0001
build: BLD-NR-2031-0001
repository: service-point-check
version: 0.2.0
written: 2031-12-05
revised: 2031-12-12
tier: B                         # promotion to C decided in AIDR-1203
purpose: Compare opening hours and notices of 24 service points in three sources and report mismatches weekly.
never_does: [write to the portal, write to the phone script, write to the notices spreadsheet]
people:
  owner: Hanna Wikström
  deputy: Elin Berg
  receiving_developer: Rasmus Holm
  accountable: Gunilla Roos
  platform_contact: Maja Lindqvist
run:
  setup: python3.12 -m venv .venv && .venv/bin/pip install -r requirements.txt
  tests: .venv/bin/python -m pytest -q          # 21 tests, oracle cases in tests/oracle/cases.yaml
  report_on_synthetic: .venv/bin/python -m spcheck.cli --week auto --data data/synthetic
  scheduled: Mondays 06:00 by the scheduled-job template
data_and_secrets:
  classes: public and internal; no personal data of residents
  inputs: [PORTAL_EXPORT, PHONE_EXPORT, NOTICES_EXPORT]      # read-only paths set by the platform
  secret: WIKI_TOKEN                                         # secret store, never in the repository
change:
  branch: feature branch, pull request to main
  checks: unit tests, oracle cases, review by a named developer
  oracle_owner: Saga Edvardsson                              # the assistant may not edit tests/oracle/
ship:
  how: tag the release in the Git service; the platform pipeline deploys by release name
  release_name: service-point-check-0.2.0-b3
revert:
  to_previous: redeploy the previous release by its release name, not by its tag
  rollback_target: service-point-check-0.1.0-b2              # the release name, not the tag v0.1.0
  last_tested: 2031-12-12
switch_off: disable the job in the scheduled-job template; the manual weekly comparison resumes
restore: clone the repository, redeploy the release, re-enter WIKI_TOKEN from the secret store (RST-NR-2031-0001, 2031-12-05, passed)
known_risks:
  - A change in the portal export format stops the run; the failure alert goes to the e-services channel.
  - WIKI_TOKEN expires; renewal is the owner's task.
  - The deputy and the owner are in the same team.
decisions: [docs/adr/0001-scheduled-job-template.md, docs/adr/0002-read-only-sources.md, docs/adr/0003-plain-markdown-report.md]
ends: 2033-08-31
id: HND-HM-2031-0001
build: BLD-HM-2031-0001
repository: claims-doc-check
version: 0.1.0
written: 2031-12-04
revised: 2031-12-12
tier: C                         # since the tier decision of 2031-09-18
purpose: List the documents missing from motor property-damage claim files and draft a request text for the handler to review.
scope: [motor_collision, motor_parking_damage, motor_glass]
out_of_scope: motor_injury       # rows are written as OUT_OF_SCOPE and not processed
never_does: [decide a claim, rank handlers or claimants, send a message, write to the claims core, read free-text fields, log more than claim identifiers]
people:
  owner: Katarzyna Nowak
  deputy: Marek Zieliński
  receiving_developer: Tomasz Wrona
  accountable: Joanna Kaczmarek
  data_protection: Dr. Sabine Vogel     # screening AIDR-1251
run:
  setup: python3.12 -m venv .venv && .venv/bin/pip install -r requirements.txt
  tests: .venv/bin/python -m pytest -q          # 18 tests, 25 oracle cases in tests/oracle/cases.yaml
  on_synthetic: .venv/bin/python -m cdcheck.cli --input data/synthetic/claims_synthetic.csv --out out/
  in_production: handlers start the job "claims-doc-check run" in the claims analytics workspace
data_and_secrets:
  classes: claimant personal data (claim identifier, claim type, document types, country, language)
  production_export: read only by the identity of the group pipeline; never by the builder or the assistant
  secrets_in_repository: none
change:
  branch: feature branch, pull request to main, review by the receiving developer
  rules: rules.yaml changes only on a written request from the head of claims operations' office
  oracle_owner: Marek Zieliński          # cases and quality sample; the assistant may not edit tests/oracle/
ship:
  how: tag the release; the group pipeline builds and deploys by release name
  release_name: claims-doc-check-0.1.0
revert:
  to_previous: disable the job; handlers return to the manual check
  last_tested: 2031-12-09
switch_off: set the job "claims-doc-check run" to disabled in the group pipeline
restore: clone the repository, redeploy the release by name (RST-HM-2031-0001, 2031-11-14, passed)
known_risks:
  - A new column in the claims export stops the run.
  - An unknown language value falls back to English drafts (remark R2 of TKT-HM-2031-0001).
decisions: [docs/adr/0001-property-damage-only.md, docs/adr/0002-draft-only.md]
ends: 2033-08-31
id: TKT-NR-2031-0001
build: BLD-NR-2031-0001
handover_pack: HND-NR-2031-0001
version_tested: 0.2.0
run_by: Rasmus Holm              # developer, integration and data team; did not build it
date: 2031-12-11
owner_present: false             # reachable by telephone; every question counts as a gap
item_time: 06h25m             # sum of the items; the test ran 08:30 to 16:30 with breaks
change_task: report a notice whose end date is earlier than its start date as a mismatch
items:
  T1_find:       {result: pass, minutes: 15}
  T2_run_tests:  {result: pass, minutes: 25}
  T3_run_report: {result: pass, minutes: 30}
  T4_change:     {result: pass, minutes: 95, note: oracle case for the test branch written by Saga Edvardsson, branch discarded}
  T5_ship:       {result: pass, minutes: 40, target: test}
  T6_revert:
    result: fail
    minutes: 70
    finding: Rollback note names tag v0.1.0; the scheduled-job template accepts a release name only.
    gap_questions_to_owner: 1
  T7_switch_off: {result: pass, minutes: 20}
  T8_restore:    {result: pass, minutes: 45}
  T9_explain:    {result: pass, minutes: 35}
  T10_secrets:   {result: pass, minutes: 10}
result: fail
fixes:
  - {item: T6_revert, action: note names service-point-check-0.1.0-b2, done: 2031-12-12, by: Hanna Wikström}
retest:
  date: 2031-12-12
  items: [T6_revert]
  run_by: Rasmus Holm
  result: pass
result_final: pass
evidence_valid_until: 2032-12-11   # sooner if the owner, the receiver or the template changes
promotion:
  from: B
  to: C
  signals: [used by service-point managers in other units, runs unattended from 2031-12-22]
  criteria: promotion_criteria.yaml@1.0
  met:
    name: {owner_and_deputy: true, deputy_drill: 2031-12-02}
    understand: {one_page_data_flow: true, independent_cases: 20, written_by: Saga Edvardsson, last_run: 2031-12-12}
    revert: {switch_off: 2031-12-11, manual_fallback: 2031-12-10, restore_test: 2031-12-05}
    data_and_access: {data_classes_recorded: true, screening: 2031-12-03, dpia_needed: false, anonymous_access: none}
    support_and_sunset: {support_path: e-services channel, then integration and data team, review_months: 6, sunset: 2033-08-31}
  decided: 2031-12-15
  decision_record: AIDR-1203
  decided_by: Gunilla Roos
  promoted_by: platform-pipeline-identity
id: TKT-HM-2031-0001
build: BLD-HM-2031-0001
handover_pack: HND-HM-2031-0001
version_tested: 0.1.0
run_by: Tomasz Wrona             # developer, Group Application Development, Polish service centre
date: 2031-12-09
owner_present: false             # reachable by telephone; every question counts as a gap
item_time: 05h50m
change_task: accept video as an alternative to photos for motor_parking_damage
items:
  T1_find:       {result: pass, minutes: 10}
  T2_run_tests:  {result: pass, minutes: 20}
  T3_run_report: {result: pass, minutes: 25}
  T4_change:     {result: pass_with_remark, minutes: 105, remark: R1}
  T5_ship:       {result: pass, minutes: 40, target: test}
  T6_revert:     {result: pass, minutes: 30}
  T7_switch_off: {result: pass, minutes: 15}
  T8_restore:    {result: pass, minutes: 40}
  T9_explain:    {result: pass_with_remark, minutes: 55, remark: R2}
  T10_secrets:   {result: pass, minutes: 10}
result: pass_with_remarks
remarks:
  R1:
    finding: README does not mention tests/oracle/cases.yaml in its change section; found after the second test run.
    action: add the oracle file and its owner to the README
    owner: Katarzyna Nowak
    due: 2031-12-16
    closed: 2031-12-12
  R2:
    finding: An unknown language value silently produces an English draft that the handler may not notice.
    action: count fallbacks in the run summary (counts only, no claim text)
    owner: Marek Zieliński
    due: 2032-01-13
    closed: null
rule: remarks may not concern a gate item; an open remark is reviewed at the next 90-day confirmation
evidence_valid_until: 2032-12-09   # sooner if the owner, the receiver or the pipeline job changes
criteria:
  tier: C
  reference: promotion_criteria.yaml@1.0
  name: {owner_and_deputy: true, deputy_drill: 2031-12-03}
  understand: {one_page_data_flow: true, independent_cases: 25, written_by: Marek Zieliński and the quality sample, last_run: 2031-12-09}
  revert: {switch_off: 2031-12-09, manual_fallback: 2031-12-09, restore_test: 2031-11-14}
  data_and_access: {screening: AIDR-1251, dpia_needed: false, anonymous_access: none}
  support_and_sunset: {support_path: claims operations' office, then Group Application Development queue, review_months: 6, sunset: 2033-08-31}
promotion: not applicable; Tier C since 2031-09-18

The request to the receiving developer, in the form Chapter 16 prints it (Nordvik Region):

Request to the receiving developer
Build: BLD-NR-2031-0001, service-point-check 0.2.0, Tier B, Tier C requested
Question: review before promotion; please run docs/handover.yaml, section run
Failing item: none known; the revert section was last tested 2031-12-05
Attached: intent page, HND-NR-2031-0001, build record, RUN records for src/spcheck/compare.py
Needed by: 2031-12-11. After that the weekly report stays manual.
Not asked: a rewrite. Please say what to change and I will make the change.

A.17 Builder learning plan and competence record

Owned by Chapter 21. The learning plan decides the next thing to learn and the tier of build the builder may own; it is shared with the builder, the coach and the second assessor, and it is never used for pay, rating, discipline or telemetry on named persons. The competence record keeps assisted and unaided entries apart, names an assessor outside the line and expires. The Nordvik Region files are as Chapter 21 prints them. The Hansa Mutual plan is composed from the paragraph of Chapter 21 (the opening date and the steps are composed; the second assessor is named by role because the chapter does not name the person).

# BLP-NR-2031-01: builder learning plan (Nordvik Region, illustrative)
id: BLP-NR-2031-01
builder: Hanna Wikström            # product owner, e-services
build: BLD-NR-2031-0001            # service-point-check
opened: 2031-11-03
review_every_days: 90
next_review: 2032-02-01
coach: Rasmus Holm                 # receiving developer
second_assessor: Saga Edvardsson   # quality engineer, outside the builder's line
ai_off_block: {hours_per_fortnight: 2, produces: a written artefact and an unaided sample}
purpose: decide the next thing to learn and the tier of build the builder may own
shared_with: [builder, coach, second_assessor]
not_used_for: [pay, rating, discipline, tool telemetry on named persons]
steps:
  - order: 1
    learn: habits (intent, tier, specification, tests the assistant did not write)
    evidence: SPC-NR-2031-0001 approved by someone else
    status: met
  - order: 2
    learn: read a change and explain it in own words
    evidence: weekly diff explanation, assistant off, marked by the coach
    status: in_progress
  - order: 3
    learn: write and debug a small function unaided
    evidence: unaided work sample, marked by the second assessor
    status: planned
stop_sign: personal data of people outside the team, money, or a second unit
# CMP-NR-2031-01: competence record (Nordvik Region, illustrative)
id: CMP-NR-2031-01
person: Hanna Wikström
role: builder, Tier B and C (service-point-check)
recorded: 2031-12-19
assessor: Saga Edvardsson          # second assessor, outside the line
valid_days: 180                    # evidence about unaided performance
entries:
  - skill: frame and specify
    condition: assisted
    evidence: SPC-NR-2031-0001, rules R1 to R3 and the never-do rule
    result: met
    date: 2031-10-15
  - skill: explain a change
    condition: unaided
    evidence: 45-minute session on a six-change pull request
    result: explained 5 of 6; missed the change to date handling
    date: 2031-12-19
  - skill: revert
    condition: unaided, with the runbook
    evidence: RST-NR-2031-0001, drill led by the deputy
    result: pass after the rollback note was corrected on 2031-12-12
    date: 2031-12-19
reader_limits: [builder, coach, second_assessor]
# BLP-HM-2031-01: builder learning plan (Hansa Mutual, illustrative and composed)
id: BLP-HM-2031-01
builder: Katarzyna Nowak           # team lead, motor claims, Polish service centre
build: BLD-HM-2031-0001            # claims-doc-check
opened: 2031-11-10                 # composed
review_every_days: 90
next_review: 2032-02-08
coach: Tomasz Wrona                # receiving developer
second_assessor: "a developer of Group Application Development who is not the coach"
manager: Joanna Kaczmarek
ai_off_block: {hours_per_fortnight: 2, where: claims analytics workspace, produces: a written artefact and an unaided sample}
purpose: decide the next thing to learn and the tier of build the builder may own
shared_with: [builder, coach, second_assessor, deputy (the plan for the build, not the competence record)]
governed_by: [works agreement KBV-IT-2024-DE, interim Betriebsrat agreement]
not_used_for: [pay, rating, discipline, tool telemetry on named persons]
path: claims analytics development path; leads towards a data analyst's role in claims operations, not towards the development organisation
steps:
  - order: 1
    learn: habits (intent, tier, specification, tests the assistant did not write)
    evidence: SPC-HM-2031-0001 approved by someone else, oracle written by the deputy
    status: met
  - order: 2
    learn: read a change and explain it in own words
    evidence: weekly diff explanation, assistant off, marked by the coach
    status: in_progress
  - order: 3
    learn: write and debug a small function unaided
    evidence: unaided work sample, marked by the second assessor
    status: planned
stop_sign: free-text claim fields, injury claims, or a second centre or market

A.18 Register entry and builder lane

Owned by Chapter 17. The register is the organisation's list of builds; the lane is the provision that makes the sanctioned route easier than the unsanctioned one. The register entry never holds the data itself, only its classes. The Nordvik Region entry and lane are as Chapter 17 prints them, with the restore-test date of Chapter 14 (the chapter prints 2031-12-12).

register: BREG-NR
entry: BLD-NR-2031-0001
name: service-point-check
owner: Hanna Wikström
deputy: Elin Berg
manager: Gunilla Roos
tier: C                         # B as an on-demand tool; C from scheduled running (AIDR-1203)
tier_decided_on: 2031-12-15
data_classes: [public, internal]
personal_data: none
runs: scheduled, Mondays 06:00, scheduled-job template
receiving_developer: Rasmus Holm
repository: service-point-check
sunset: 2033-08-31
evidence:
  tests:        {result: pass, date: 2031-12-08, valid_days: 90}
  takeover:     {result: pass_after_fix, date: 2031-12-12, valid_days: 90}
  restore_test: {result: pass, date: 2031-12-05, valid_days: 90}
next_review: 2032-03-04         # 90 days after the oldest evidence
discovered_by: declared
lane: LANE-NR-2031-01
organisation: Nordvik Region
decision: AIDR-1200
opened_on: 2031-10-06
owner: Maja Lindqvist            # platform team lead
workspace:
  assistant: approved assistant of the builder workspace
  usage_modes: [2, 3]            # Mode 3 only in the sandbox; Mode 5 not provided
  level_4: not used for any action class
  data: synthetic only
  production_reach: none
  secrets: store injects at run time; never in repository or prompt
templates:
  - name: builder-template-scheduled-job
    for_tiers: [B, C]
    version: 0.3.0
    pipeline_rules: [secrets_scan, ownership_file, oracle_cases_present, synthetic_data_only]
register: BREG-NR
receiving_developers:
  - {name: Rasmus Holm, team: integration and data, allowance: half a day a fortnight per build, first quarter}
support:
  office_hours: Tuesdays 14:00-15:00
  contact: platform team
service_level_note:
  first_response: 2 working days
  template_defects_fixed: 10 working days
  stop_path: platform team disables the job or revokes the pipeline identity; no prior justification needed
  evidence: dry run with a volunteer builder, 2031-10-27, fixes recorded
  valid_days: 90

The skeleton of the repository template that the lane provides, in the form Chapter 17 prints it:

builder-template-scheduled-job/
  AGENTS.md                  # rules file for the assistant, signed by the owner
  README.md                  # what the tool is for, how to run it, who to call
  pipeline.yml               # stages test, scan, deploy; fails on secrets and on missing files
  pyproject.toml             # pinned dependencies, Python 3.12
  src/                       # the tool's code
  tests/
    test_smoke.py            # one passing test, so the first pipeline run is green
    oracle/cases.yaml        # empty, to be filled by someone other than the builder
  data/synthetic/            # synthetic data only; the pipeline rejects other files
  docs/
    build-record.yaml        # BLD record skeleton, tier decision, gates
    specification.yaml       # SPC record skeleton
    ownership.yaml           # OWN record skeleton: owner, deputy, review dates, sunset
    runbook.yaml             # RBK record skeleton: start, stop, restore
    handover.yaml            # HND record skeleton
    adr/0001-template.md
  CHANGELOG.md

A.19 Checks that can be run mechanically

Owned by Chapters 5, 6, 12, 15 and 21. Each script reads a record from this appendix and reports what is missing or contradictory. A script that passes proves that the record is complete and consistent, not that the build is good: the gate is met by a person reading the artefact. The scripts need Python 3.12 and PyYAML, read files and change nothing. Run them from the repository, for example python3 check_intent.py docs/build-record.yaml, python3 classify_tier.py docs/build-record.yaml, python3 check_ownership.py docs/ownership.yaml 2032-01-15, python3 check_dependencies.py requirements.txt dependency-policy.yaml and python3 check_plan.py docs/learning-plan.yaml 2032-01-15. The scripts are the ones printed in the chapters, unchanged except that the ownership check and the plan check are shown with the file name above them.

check_intent.py: Step 1 gate: is the intent section of a build record complete? Chapter 5.

"""Step 1 gate check: is the intent section of a build record complete?"""
import re
import sys

import yaml

REQUIRED = ["purpose", "user", "problem_now", "success_measure", "done",
            "non_goals", "affected"]


def check(record: dict) -> list[str]:
    problems = []
    intent = record.get("intent") or {}
    for key in REQUIRED:
        if not intent.get(key):
            problems.append(f"intent.{key} is empty")
    purpose = str(intent.get("purpose", ""))
    if len(re.findall(r"[.!?](\s|$)", purpose)) > 1:
        problems.append("intent.purpose is more than one sentence")
    measure = intent.get("success_measure") or {}
    for key in ("what", "source", "baseline", "target"):
        if not measure.get(key):
            problems.append(f"success_measure.{key} is missing")
    baseline = measure.get("baseline") or {}
    if baseline and "value" not in baseline and not (
            baseline.get("method") and baseline.get("due")):
        problems.append("baseline needs a value, or a method and a due date")
    gate = intent.get("gate") or {}
    if gate.get("status") == "met":
        for key in ("date", "read_back_by", "approved_by"):
            if not gate.get(key):
                problems.append(f"gate.{key} is missing")
        if gate.get("read_back_by") == record.get("builder"):
            problems.append("the read-back must be by someone other than the builder")
    else:
        problems.append("gate is not met")
    return problems


if __name__ == "__main__":
    with open(sys.argv[1], encoding="utf-8") as handle:
        found = check(yaml.safe_load(handle))
    print("\n".join(found) if found else "Step 1 gate: met")
    sys.exit(1 if found else 0)

classify_tier.py: Step 2: classify a build from the five answers. Chapter 6.

"""Step 2: classify a build from the five answers. A decision aid, not a ruling."""
import sys

import yaml

STOP_SIGNS = {
    "ranks_or_decides_about_a_named_person": "ranks or decides about a named person",
    "feeds_a_high_risk_ai_system": "is or feeds a high-risk AI system",
    "security_critical_path": "sits in a security-critical path",
    "irreversible_on_production_data": "acts irreversibly on production data",
}


def classify(answers: dict) -> dict:
    stops = [text for key, text in STOP_SIGNS.items() if answers.get(key)]
    if stops:
        return {"tier": "D", "signals": stops,
                "call": ["a developer", "the data protection officer"]}
    signals = []
    if answers["data"] in ("personal", "special"):
        signals.append("personal data of people outside the team")
    if answers.get("money"):
        signals.append("money")
    if answers.get("organisational_record"):
        signals.append("a record of the organisation")
    if answers["users"] in ("units", "public"):
        signals.append("more than one unit")
    if answers.get("unattended"):
        signals.append("unattended running")
    call = []
    if answers["data"] in ("personal", "special"):
        call.append("the data protection officer")
    if answers["data"] == "special":
        call.append("a developer, before anything is built")
    if signals:
        return {"tier": "C", "signals": signals, "call": call or ["a developer"]}
    if answers["users"] == "team" or answers["data"] == "internal":
        return {"tier": "B", "signals": ["colleagues in one unit use it"], "call": []}
    return {"tier": "A", "signals": [], "call": []}


if __name__ == "__main__":
    with open(sys.argv[1], encoding="utf-8") as handle:
        data = yaml.safe_load(handle)
    result = classify((data.get("tier") or data)["answers"])
    print(f"Tier {result['tier']}")
    for signal in result["signals"]:
        print(f"  signal: {signal}")
    for who in result["call"]:
        print(f"  call: {who}")

check_dependencies.py: Step 8: fail if requirements.txt names a package the policy does not allow. Chapter 12.

"""Fail if requirements.txt names a package the policy file does not allow."""
import re
import sys
from pathlib import Path

import yaml


def names(requirements: str) -> set[str]:
    found = set()
    for line in requirements.splitlines():
        line = line.split("#")[0].strip()
        if line:
            found.add(re.split(r"[=<>~!\[; ]", line, maxsplit=1)[0].lower())
    return found


def main(requirements="requirements.txt", policy="dependency-policy.yaml") -> int:
    allowed = {p["name"].lower() for p in yaml.safe_load(Path(policy).read_text())["allowed"]}
    unknown = sorted(names(Path(requirements).read_text()) - allowed)
    for name in unknown:
        print(f"not in dependency policy: {name}")
    return 1 if unknown else 0


if __name__ == "__main__":
    sys.exit(main(*sys.argv[1:]))

check_ownership.py: Step 11: check an ownership record. Chapter 15.

"""Check an ownership record: required fields present, review not overdue."""
import sys
from datetime import date, timedelta
import yaml

REQUIRED = ["id", "owner", "deputy", "accountable", "review", "sunset", "documents"]

def check(path, today):
    rec = yaml.safe_load(open(path, encoding="utf-8"))
    problems = [f"missing field: {k}" for k in REQUIRED if k not in rec]
    if problems:
        return problems
    rev = rec["review"]
    if today > rev["next_due"]:
        problems.append(f"review overdue since {rev['next_due']}")
    if rev["confirmed"] + timedelta(days=rev["every_days"]) != rev["next_due"]:
        problems.append("next_due does not follow from confirmed and every_days")
    drill = rec["deputy"].get("drill", {})
    if drill.get("result") != "passed" or not drill.get("without_assistant"):
        problems.append("explain-it-back drill missing or not passed without the assistant")
    if today >= rec["sunset"]["date"]:
        problems.append("sunset date reached: renew or retire")
    return problems

if __name__ == "__main__":
    today = date.fromisoformat(sys.argv[2]) if len(sys.argv) > 2 else date.today()
    found = check(sys.argv[1], today)
    print("\n".join(found) if found else "ownership record in order")
    sys.exit(1 if found else 0)

check_plan.py: Builder learning plan check. Chapter 21.

# plancheck.py: check a builder learning plan (PyYAML, Python 3.12)
import sys, datetime as dt, yaml

plan = yaml.safe_load(open(sys.argv[1]))
today = dt.date.fromisoformat(sys.argv[2])
assert plan["second_assessor"] != plan["coach"], "assessor must differ from coach"
assert "pay" in plan["not_used_for"], "the plan must say what it is not used for"
if today > plan["next_review"]:
    print("OVERDUE: review of", plan["id"], "was due", plan["next_review"])
else:
    print(plan["id"], "ok; next review", plan["next_review"])
for step in plan["steps"]:
    print(step["order"], step["status"], "-", step["learn"])

A.20 Reconciliation

The records in this appendix were reconciled with the chapters that own them, and the facts that the chapters established are in docs/EXAMPLE_ORGANISATIONS.md.

Sources

  1. Nordqvist, G. (2026). AI Developer: How the Developer Role Changes When AI Is Used Broadly in European Enterprises and Public Authorities. The Transformed Architecture, Book 12. (86, Nordqvist, 2026)