Add lighthouse showcase pack (v0.1 runnable, v0.2 draft); fix manual-check cycle and blocked-step feedback (RBT-WP-0004-T05)
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Work Records / validate (push) Has been cancelled

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: sonnet
Assistant-Process: 295952@bnt-lap001
Assistant-Session: e93f64ad-516c-46eb-9666-aad8d300c477
This commit is contained in:
tegwick 2026-09-29 10:36:29 +02:00
parent f9fb9008bf
commit b47c948a7e
8 changed files with 387 additions and 12 deletions

View file

@ -0,0 +1,147 @@
# DRAFT - not loaded by the engine. Written against docs/pack-spec-v0.2-proposal.md.
# Purpose: an acceptance test for the proposed model. If the model cannot express
# this story without special cases, the proposal must change. When v0.2 is adopted
# this file becomes pack.yaml (and the v0.1 file is retired or kept as a
# compatibility fixture).
spec: runbook-pack/v0.2
id: rtut.showcase-lighthouse
title: "Showcase: a night at the lighthouse"
owner: runbook-tutorials
outcome: You have played a short interactive story that touches every feature of the runbook engine, using only harmless echo, printf and a scratch file under /tmp/rtut-.
exercise_status: unexercised
engine: native
confirm_phrase: I understand this cannot be undone
framing: # shown first; things to keep in mind, not gates
- This showcase never handles a secret. Datapoints hold references and facts, never secret values.
- Only /tmp/rtut-* is ever written or removed.
- Several things below can be done in any order; the guide recommends one.
actors:
- id: keeper
role: Lighthouse keeper
description: Runs the lamp and the signals. Does the everyday work.
privileges: [light-lamp, sound-signal, write-logbook]
- id: harbourmaster
role: Harbour master
description: Approves the irreversible closing of the night's log.
privileges: [close-log, waive-lamp-check]
datapoints:
- {id: keeper_name, label: Keeper's name, type: string, pattern: "[A-Za-z]+", default: Ada, dimensions: {actor: keeper, kind: identity}, ask_in: dusk}
- {id: lamp_fuel, label: Lamp fuel, type: enum, values: [whale-oil, paraffin, electric], default: paraffin, dimensions: {actor: keeper, kind: equipment}, ask_in: dusk}
- {id: weather, label: Tonight's weather, type: enum, values: [clear, fog, storm], default: fog, dimensions: {kind: condition}, ask_in: dusk}
- {id: motto, label: Motto, type: text, default: Steady light for those at sea, dimensions: {actor: keeper, kind: identity}, ask_in: dusk}
- {id: logdir, label: Scratch folder, type: path, pattern: "/tmp/rtut-[a-z0-9-]+", default: /tmp/rtut-lighthouse, dimensions: {kind: location, subsystem: logbook}, ask_in: dusk}
- {id: lamp_lit, label: Lamp is lit, type: bool, dimensions: {actor: keeper, kind: state}, produced_by: light-lamp, confirmed_by: lamp-check}
stages:
- id: dusk
title: Dusk - prepare
goal: The lamp is lit and the weather is known before dark.
steps: # no requires among these: they are parallel; list order is the recommended order
- id: read-the-sign
title: Read the poem over the door
actor: keeper
owner: the-lighthouse
command: printf '%s\n' 'Tower of stone, and a patient flame,' 'you keep no name, yet call each name' 'across the dark, across the foam:' 'come home, come home, come home.'
reversible: true
verify: {done_when: You read the poem, expect: manual}
- id: light-lamp
title: Light the lamp
actor: keeper
owner: the-lighthouse
uses: [keeper_name, lamp_fuel]
produces: [lamp_lit]
command: echo "{{keeper_name}} lights the {{lamp_fuel}} lamp."
reversible: true
verify: {done_when: The lamp is lit, expect: manual}
- id: read-the-sky
title: Read the sky
actor: keeper
owner: the-lighthouse
uses: [weather]
command: echo "Weather tonight - {{weather}}."
reversible: true
verify: {done_when: You know the weather, command: 'echo "Weather tonight - {{weather}}."', expect: output-contains, contains: "{{weather}}"}
gates:
- id: lamp-check
title: The lamp burns steadily
goal: safe-navigation # quality objective; a canon standard id where one exists
blocking: true
checks:
- {done_when: The light is visible from the water, expect: manual}
- {datapoints_ok: [lamp_lit]}
waivable: {by: harbourmaster, needs_reason: true}
- id: night
title: Night watch - give warning
goal: Ships are warned in a way that suits the weather.
requires: [gate:lamp-check]
steps:
- id: warn
title: Warn the ships
actor: keeper
owner: the-lighthouse
uses: [keeper_name, weather]
one_of: # alternatives: any one satisfies "warn"; the guide recommends by weather
- id: sound-horn
title: Sound the fog horn
recommended_when: {weather: [fog]}
command: echo "{{keeper_name}} sounds the horn through the {{weather}} night."
reversible: true
verify: {done_when: The horn has sounded, expect: manual}
- id: wave-flags
title: Signal with flags
recommended_when: {weather: [clear]}
command: echo "{{keeper_name}} signals with flags in the {{weather}} night."
reversible: true
verify: {done_when: The flags were seen, expect: manual}
- id: shelter
title: Bar the door and wait it out
recommended_when: {weather: [storm]}
command: echo "{{keeper_name}} bars the door. Nobody is warned tonight."
reversible: true
verify: {done_when: The door is barred, expect: manual}
- id: write-logbook
title: Write tonight's entry
actor: keeper
owner: the-lighthouse
requires: [warn]
uses: [keeper_name, weather, lamp_fuel, motto, logdir]
produces: [logbook_file]
command: mkdir -p {{logdir}} && echo "Night watch by {{keeper_name}} - {{weather}}, {{lamp_fuel}} lamp. {{motto}}." >> {{logdir}}/logbook.txt
reversible: true
rollback: rm -rf {{logdir}}
verify: {done_when: The logbook exists, command: 'test -s {{logdir}}/logbook.txt', expect: exit-0}
- id: dawn
title: Dawn - close the night
goal: The night is on record and the scratch space is clean.
requires: [night]
steps:
- id: close-log
title: Close the log for good
actor: harbourmaster
owner: the-lighthouse
uses: [logdir]
command: cat {{logdir}}/logbook.txt && rm -rf {{logdir}}
reversible: false # engine demands the typed confirm_phrase; no renderer can skip it
verify: {done_when: The scratch folder is gone, command: 'test ! -e {{logdir}}', expect: exit-0}
gates:
- id: night-on-record
title: Nothing left behind
goal: clean-handover
blocking: true
checks:
- {done_when: No files remain under the scratch folder, command: 'test ! -e {{logdir}}', expect: exit-0}
artefacts:
- id: logbook_file
title: The night's logbook entry
location: {datapoint: logdir, path: logbook.txt} # a reference, never a value
produced_by: write-logbook
lives_until: close-log
ownership:
- {concern: The story, owner: runbook-tutorials}

View file

@ -0,0 +1,102 @@
spec: runbook-pack/v0.1
id: rtut.showcase-lighthouse
title: "Showcase: a night at the lighthouse"
owner: runbook-tutorials
outcome: You have played a short interactive story that touches every feature of the runbook engine, using only harmless echo, printf and a scratch file. Nothing outside /tmp/rtut-* is touched.
exercise_status: unexercised
engine: native
parameters:
- {id: keeper, label: Your keeper's name, type: string, default: Ada, pattern: "[A-Za-z]+", help: One word. Used in the story.}
- {id: lamp, label: Lamp fuel, type: enum, values: [whale-oil, paraffin, electric], default: paraffin, help: "Changes only the story text."}
- {id: weather, label: Tonight's weather, type: enum, values: [clear, fog, storm], default: fog}
- {id: signal, label: Warning signal, type: enum, values: [horn, flags], default: horn, help: "In fog the horn carries; in a storm, flags are lost. Choose well."}
- {id: motto, label: The keeper's motto, type: text, default: Steady light for those at sea, help: A short phrase. Spaces are fine; quotes and shell symbols are not.}
- {id: logdir, label: Scratch folder for the logbook, type: path, default: /tmp/rtut-lighthouse, pattern: "/tmp/rtut-[a-z0-9-]+", help: Must start with /tmp/rtut-. Removed at the end of the story.}
prerequisites:
- {text: "A shell with echo, printf, cat and test (any Linux or macOS terminal)", owner: workstation}
- {text: "Nothing else. No network, no accounts, no secrets", owner: nobody}
steps:
- id: read-the-sign
title: Read the poem over the door
owner: the-lighthouse
description: A read-only step with a manual check. The engine cannot know whether you read it, so it asks you.
command: |
printf '%s\n' 'Tower of stone, and a patient flame,' 'you keep no name, yet call each name' 'across the dark, across the foam:' 'come home, come home, come home.'
verify:
done_when: You read the poem and know what the light is for
expect: manual
- id: meet-the-keeper
title: Meet the keeper
owner: the-lighthouse
description: Parameters appear inside commands. Change the name in the parameter form and this line changes.
command: echo "{{keeper}} climbs the stairs. Motto on the wall - {{motto}}."
verify:
done_when: The line names your keeper
command: echo "{{keeper}} climbs the stairs. Motto on the wall - {{motto}}."
expect: output-contains
contains: "{{keeper}} climbs"
- id: light-the-lamp
title: Light the lamp
owner: the-lighthouse
command: echo "{{keeper}} lights the {{lamp}} lamp."
verify:
done_when: The lamp is lit
command: echo "{{keeper}} lights the {{lamp}} lamp."
expect: output-contains
contains: "lights the {{lamp}} lamp"
- id: read-the-sky
title: Read the sky
owner: the-lighthouse
command: echo "Weather tonight - {{weather}}."
verify:
done_when: You know tonight's weather
command: echo "Weather tonight - {{weather}}."
expect: output-contains
contains: "{{weather}}"
- id: sound-the-signal
title: Sound the warning
owner: the-lighthouse
description: In a fuller runbook this would be a choice between alternatives. In v0.1 the choice is a parameter.
command: echo "{{keeper}} works the {{signal}} through the {{weather}} night."
verify:
done_when: The signal is given
command: echo "{{keeper}} works the {{signal}} through the {{weather}} night."
expect: output-contains
contains: "works the {{signal}}"
- id: write-the-logbook
title: Write tonight's entry in the logbook
owner: the-lighthouse
description: The only step that changes anything, and only a file under /tmp/rtut-. Reversible, so it needs a confirm, not a typed phrase.
command: mkdir -p {{logdir}} && echo "Night watch by {{keeper}} - {{weather}}, {{lamp}} lamp, signal {{signal}}. {{motto}}." >> {{logdir}}/logbook.txt
risk: changes-state
rollback: rm -rf {{logdir}}
verify:
done_when: The logbook exists and is not empty
command: test -s {{logdir}}/logbook.txt
expect: exit-0
evidence: [logbook_lines]
- id: read-the-logbook
title: Read the logbook back
owner: the-lighthouse
command: cat {{logdir}}/logbook.txt
verify:
done_when: The entry names your keeper
command: cat {{logdir}}/logbook.txt
expect: output-contains
contains: "{{keeper}}"
- id: dawn
title: Put out the lamp and tidy up
owner: the-lighthouse
description: Removes the scratch folder. A run in which every step is done can offer to mark this pack exercised.
command: rm -rf {{logdir}}
risk: changes-state
rollback: Repeat write-the-logbook to rebuild the entry.
verify:
done_when: The scratch folder is gone
command: test ! -e {{logdir}}
expect: exit-0
threat_checks:
- This showcase never handles a secret. Parameters are never secret in any runbook; do not type one here to see what happens.
- Only /tmp/rtut-* is ever written or removed; the folder pattern is enforced by the parameter.
ownership:
- {concern: The story, owner: runbook-tutorials}