Лабораторна робота 24
Лабораторна робота №24. Підсумкова автоматизована виробнича комірка
Мета: у команді спроєктувати, реалізувати й захистити відтворюваний mock-прототип виробничої комірки MERC-I5, що поєднує подавання, транспортування, ідентифікацію, координатний gate, символічне роботизоване переміщення, сортування/палетизацію, журналювання й ручне відновлення; пройти однозначні автоматичні acceptance tests до будь-якої фізичної апробації.
Результати навчання та передумови
Після роботи команда вміє:
- перетворити виробничий сценарій на версіоновану специфікацію й контракти адаптерів;
- розподілити відповідальність без розмивання safety-рішень;
- реалізувати fail-safe FSM із тайм-аутами, ідемпотентністю й audit log;
- інтегрувати mock feeder/conveyor/sensor/camera/classifier/transform/robot/gripper/storage;
- ін'єктувати відмови, блокувати automatic recovery та вимірювати метрики;
- надати машинно виконувані докази для кожного acceptance criterion;
- чітко відокремити software acceptance від фізичного приймання.
Передумови: виконані ЛР-01–23 або еквівалентні підтверджені компетентності; Python, JSON, SQLite, Git, тестування, FSM і правила безпеки MERC-I5.
Середовище виконання
| Середовище | Статус | Примітка |
|---|---|---|
| Google Colab | дозволено | Mock acceptance; SQLite :memory: |
| Локальний ПК | основне | Командна розробка, тести, журнали |
| Raspberry Pi | дозволено | Лише після фіксації середовища; у цій редакції mock-only |
| Симуляція | основне | Усі acceptance tests без мережі й обладнання |
| Фізичний комплекс MERC-I5 | не використовується | Окремий етап потребує нового дозволу та протоколу |
Організація командного проєкту
Рекомендовано 3–5 учасників. Одна особа може виконувати кілька ролей, але автор зміни не затверджує самостійно safety-критичний перехід.
| Роль | Відповідальність | Обов'язковий артефакт |
|---|---|---|
| інтегратор/safety lead | межі, FSM, safe state, merge gate | state/invariant table, safety review |
| module/API lead | adapters і конфігурація | interface contracts, mocks |
| vision/data lead | classifier, transform, UNKNOWN, storage | fixtures, data/DB evidence |
| QA/recovery lead | fault injection, acceptance runner | test matrix, logs, recovery evidence |
| documentation/release lead | відтворюваність і звіт | release manifest, commit SHA, limitations |
Для кожної зміни зберігайте автора, reviewer і acceptance IDs, яких вона стосується. Не записуйте персональні дані понад навчальний ідентифікатор, визначений закладом.
Необхідні знання та матеріали
- Python зі стандартною бібліотекою;
- зовнішній
lab24-spec.jsonіз цієї роботи; - програма ЛР-24;
- операційна безпека, допуск студентів, QR/storage model;
- commit SHA або контрольна сума переданого архіву; без секретів.
Ризики та правила безпеки
Ризики
Наскрізний сценарій накопичує ризики всіх модулів: подвійна подача, втрата деталі, хибна класифікація/координата, невдале захоплення, неправильний маршрут, частковий DB commit, повтор cycle, restart і втрата зв'язку. Успішний mock не підтверджує wiring, колізії, гальмування, зусилля чи E-Stop coverage.
Заборонені дії
- не замінювати mock апаратним адаптером у межах цієї редакції;
- не використовувати MG400 API, GPIO, UART, USB-камеру, реле, мережеві адреси або I/O;
- не приховувати
UNKNOWN, timeout чи failing acceptance; - не додавати автоматичні
ClearError,Continue, retry руху або resume після restart; - не зберігати токени, паролі, приватні IP або персональні дані;
- не оголошувати software PASS дозволом на фізичний запуск.
Умови негайної зупинки
Будь-який failed preflight, timeout, unknown/invalid transform, camera/DB/robot/gripper error, duplicate active cycle, restart, невідомий стан або порушення інваріанта. Усі outputs переходять у safe state, подія фіксується, цикл — RECOVERY_REQUIRED.
Безпечний стан
feeder=STOP, conveyor=STOP, robot=NO_NEW_GOAL, gripper=HOLD_OR_STOP, storage transaction commit/rollback завершена, причина latched, automatic resume заборонено. Конкретний фізичний стан грипера потребує окремого підтвердження.
Передпусковий чекліст
- У release manifest вказані commit SHA, середовище й
mode=simulation-only. - Немає hardware/network imports або секретів.
- Усі модулі мають mock і timeout.
- Координати/маршрути символічні.
-
UNKNOWN, duplicate, DB failure і restart входять до tests. - Кожний terminal state перевіряє safe outputs.
- Recovery потребує чотирьох ручних gates.
- Усі acceptance tests PASS до захисту.
Хід виконання роботи
- Затвердити scope, ролі, interface contracts і acceptance matrix.
- Перевірити глобальні інваріанти safe-smoke.
- Створити зовнішню специфікацію.
- Реалізувати reference mock cell або сумісний командний варіант.
- Прогнати acceptance runner і зберегти JSON evidence.
- Проаналізувати faults, метрики, обмеження й release manifest.
- Провести командний захист; фізичний етап не виконувати.
Методичні вказівки й теоретичні відомості
1. Архітектурний контракт
Оркестратор бачить лише адаптери:
| Адаптер | Команди | Підтвердження | Safe state |
|---|---|---|---|
| feeder | feed_one(cycle) | exactly-one / fault | stop |
| conveyor | transport(cycle) | sensor + stop ack | stop |
| camera/classifier | observe(cycle) | class/confidence/timestamp | no decision |
| transform | map(point, calibration_id) | inside/error + frame IDs | no target |
| robot | submit(plan_id) / cancel | ack/feedback/result | no new goal |
| gripper | open/close/stop/status | confirmed/unknown | hold-or-stop by validated driver |
| storage | transaction/event | commit/rollback/idempotent | consistent DB |
2. Acceptance tests як контракт
Тест має identifier, передумови, stimulus, однозначне expected і evidence. Його не можна «прийняти вручну», якщо автоматичний assertion падає. Фізичні acceptance tests є окремим набором і не входять до software PASS.
3. Ідемпотентність та recovery
Cycle ID не виконується двічі. Повтор completed ID повертає ідемпотентний результат без нових goals. Якщо стан незавершений або процес перезапущено, система не вгадує позицію, а переходить у RECOVERY_REQUIRED.
Текстовий опис рисунка: preflight пропускає цикл до feeder/conveyor, після sensor stop камера, classifier і transform створюють лише валідний план; mock-robot/gripper виконують його, SQLite журналює commit, а будь-який fault або restart зупиняє всі виходи та вимагає ручної reconciliation.
flowchart LR
SPEC["Versioned spec + cycle ID"] --> PRE["Preflight"]
PRE --> FEED["Mock feeder"]
FEED --> CONV["Mock conveyor + sensor"]
CONV --> OBS["Mock camera/classifier"]
OBS --> GATE{"class + confidence + transform valid?"}
GATE -->|ні| REJ["REJECT / no goal"]
GATE -->|так| RG["Mock robot + GripperAdapter"]
RG --> DB["SQLite :memory: transaction + audit"]
DB --> DONE["COMPLETE"]
PRE -->|fault| REC["RECOVERY_REQUIRED"]
FEED -->|timeout| REC
CONV -->|timeout| REC
OBS -->|lost| REC
RG -->|no ack| REC
DB -->|rollback| REC
Рис. 1. Reference mock-архітектура підсумкової комірки
4. Джерела
- структура лабораторної програми MERC-I5;
- цільовий безпечний драйвер MG400;
- QR і транзакційна модель;
- допуск і перевірка без руху.
Acceptance criteria
| ID | Перевірка | Умова PASS | Evidence |
|---|---|---|---|
| AC-01 | preflight/mode | лише simulation-only, жодних секретних ключів | config audit |
| AC-02 | normal cycle | COMPLETE, один ledger row, safe outputs | outcome + DB count |
| AC-03 | UNKNOWN | REJECT, robot=NO_NEW_GOAL | transition/output log |
| AC-04 | duplicate cycle | IDEMPOTENT, ledger count не зростає | before/after count |
| AC-05 | feeder timeout | RECOVERY_REQUIRED, all outputs safe | fault log |
| AC-06 | camera lost | RECOVERY_REQUIRED, no pick | fault log |
| AC-07 | robot timeout | RECOVERY_REQUIRED, no retry | fault log |
| AC-08 | sensor/transport timeout | RECOVERY_REQUIRED, conveyor STOP | fault log |
| AC-09 | DB failure | rollback, RECOVERY_REQUIRED, no ledger row | DB count |
| AC-10 | restart mid-cycle | RECOVERY_REQUIRED, no resume | transition log |
| AC-11 | manual recovery | неповні gates blocked, повні gates → IDLE | assertions |
| AC-12 | audit completeness | cycle ID, states, reason; без секретів | evidence JSON |
Усі 12 критерії обов'язкові. Команда може додавати, але не видаляти їх.
Виконання лабораторної роботи
Крок 1. Safe-smoke глобальних інваріантів
# safe-smoke
SAFE = {"feeder":"STOP", "conveyor":"STOP", "robot":"NO_NEW_GOAL", "gripper":"HOLD_OR_STOP"}
def terminal_safe(outputs: dict[str, str]) -> bool:
return outputs == SAFE
def recover(operator_ack: bool, cause_removed: bool, reconciled: bool, area_clear: bool) -> bool:
return all((operator_ack, cause_removed, reconciled, area_clear))
assert terminal_safe(dict(SAFE))
assert not recover(True, True, False, True)
assert recover(True, True, True, True)
print("safe-smoke: final-cell invariants PASS")
Очікуваний результат: safe-smoke: final-cell invariants PASS.
Критерій правильності: safe state має всі чотири outputs; reconciliation не опціональна.
Якщо результат не отримано: проєкт не допускається до integration tests.
Крок 2. Зовнішня специфікація
Збережіть як lab24-spec.json.
{
"mode":"simulation-only",
"confidence_min":0.80,
"timeouts":{"feed":3,"transport":4,"robot":3,"gripper":2},
"routes":{"red-cylinder":"BOX_A","blue-cube":"BOX_B"},
"cases":[
{"acceptance_id":"AC-02","id":"normal","cycle":"C-001","feed":2,"transport":3,"camera":true,"label":"red-cylinder","confidence":0.92,"robot":2,"gripper":1,"grip_ok":true,"db_ok":true,"restart":false,"expected":"COMPLETE"},
{"acceptance_id":"AC-02","id":"boundary-timeouts","cycle":"C-009","feed":3,"transport":4,"camera":true,"label":"blue-cube","confidence":0.80,"robot":3,"gripper":2,"grip_ok":true,"db_ok":true,"restart":false,"expected":"COMPLETE"},
{"acceptance_id":"AC-03","id":"unknown","cycle":"C-002","feed":1,"transport":1,"camera":true,"label":"UNKNOWN","confidence":0.30,"robot":0,"gripper":0,"grip_ok":false,"db_ok":true,"restart":false,"expected":"REJECT"},
{"acceptance_id":"AC-04","id":"duplicate","cycle":"C-001","feed":0,"transport":0,"camera":true,"label":"red-cylinder","confidence":1.0,"robot":0,"gripper":0,"grip_ok":true,"db_ok":true,"restart":false,"expected":"IDEMPOTENT"},
{"acceptance_id":"AC-05","id":"feed-timeout","cycle":"C-003","feed":4,"transport":1,"camera":true,"label":"red-cylinder","confidence":0.90,"robot":1,"gripper":1,"grip_ok":true,"db_ok":true,"restart":false,"expected":"RECOVERY_REQUIRED"},
{"acceptance_id":"AC-06","id":"camera-lost","cycle":"C-004","feed":1,"transport":1,"camera":false,"label":"red-cylinder","confidence":0.90,"robot":1,"gripper":1,"grip_ok":true,"db_ok":true,"restart":false,"expected":"RECOVERY_REQUIRED"},
{"acceptance_id":"AC-07","id":"robot-timeout","cycle":"C-005","feed":1,"transport":1,"camera":true,"label":"blue-cube","confidence":0.90,"robot":4,"gripper":1,"grip_ok":true,"db_ok":true,"restart":false,"expected":"RECOVERY_REQUIRED"},
{"acceptance_id":"AC-08","id":"sensor-timeout","cycle":"C-006","feed":1,"transport":5,"camera":true,"label":"red-cylinder","confidence":0.90,"robot":1,"gripper":1,"grip_ok":true,"db_ok":true,"restart":false,"expected":"RECOVERY_REQUIRED"},
{"acceptance_id":"AC-09","id":"db-failure","cycle":"C-007","feed":1,"transport":1,"camera":true,"label":"red-cylinder","confidence":0.90,"robot":1,"gripper":1,"grip_ok":true,"db_ok":false,"restart":false,"expected":"RECOVERY_REQUIRED"},
{"acceptance_id":"AC-10","id":"restart","cycle":"C-008","feed":1,"transport":1,"camera":true,"label":"red-cylinder","confidence":0.90,"robot":1,"gripper":1,"grip_ok":true,"db_ok":true,"restart":true,"expected":"RECOVERY_REQUIRED"}
]
}
Очікуваний результат: специфікація містить AC-02…AC-10 cases і не містить фізичних параметрів.
Критерій правильності: JSON валідний; timeout + 1 використано у граничних faults.
Якщо результат не отримано: виправити spec до реалізації; не переносити значення в код.
Крок 3. Reference mock і acceptance runner
Збережіть як lab24_acceptance.py.
from __future__ import annotations
import argparse
import json
import sqlite3
from dataclasses import asdict, dataclass, field
from enum import Enum
from pathlib import Path
class State(str, Enum):
IDLE="IDLE"; PREFLIGHT="PREFLIGHT"; FEEDING="FEEDING"; TRANSPORTING="TRANSPORTING"
INSPECTING="INSPECTING"; PICKING="PICKING"; PERSISTING="PERSISTING"
COMPLETE="COMPLETE"; REJECT="REJECT"; IDEMPOTENT="IDEMPOTENT"
FAULT="FAULT"; RECOVERY_REQUIRED="RECOVERY_REQUIRED"
SAFE = {"feeder":"STOP", "conveyor":"STOP", "robot":"NO_NEW_GOAL", "gripper":"HOLD_OR_STOP"}
@dataclass
class Outcome:
acceptance_id: str
case_id: str
cycle_id: str
state: str
reason: str
route: str | None
outputs: dict[str, str]
ledger_rows: int
transitions: list[str] = field(default_factory=list)
def no_secret_keys(value: object) -> bool:
if isinstance(value, dict):
for key, item in value.items():
if any(word in str(key).lower() for word in ("password", "token", "private_key")):
return False
if not no_secret_keys(item):
return False
elif isinstance(value, list):
return all(no_secret_keys(item) for item in value)
return True
class ReferenceCell:
def __init__(self, spec: dict) -> None:
if spec.get("mode") != "simulation-only" or not no_secret_keys(spec):
raise ValueError("AC-01 failed")
self.spec = spec
self.db = sqlite3.connect(":memory:")
self.db.execute("CREATE TABLE ledger(cycle_id TEXT PRIMARY KEY, route TEXT NOT NULL, status TEXT NOT NULL)")
self.db.commit()
self.state = State.IDLE
self.outputs = dict(SAFE)
self.log: list[str] = []
def reset_runtime(self) -> None:
self.state = State.IDLE; self.outputs = dict(SAFE); self.log = []
def move(self, target: State, reason: str) -> None:
self.log.append(f"{self.state.value}->{target.value}:{reason}")
self.state = target
def safe_stop(self) -> None:
self.outputs = dict(SAFE)
def fail(self, reason: str) -> None:
self.safe_stop(); self.move(State.FAULT, reason); self.move(State.RECOVERY_REQUIRED, "manual-reconciliation")
def ledger_count(self) -> int:
return int(self.db.execute("SELECT COUNT(*) FROM ledger").fetchone()[0])
def run(self, case: dict) -> Outcome:
self.reset_runtime(); cycle = str(case["cycle"]); route = None; reason = "ok"
self.move(State.PREFLIGHT, "start")
if self.db.execute("SELECT 1 FROM ledger WHERE cycle_id=?", (cycle,)).fetchone():
self.move(State.IDEMPOTENT, "completed-cycle-already-recorded")
else:
self.move(State.FEEDING, "preflight-pass"); self.outputs["feeder"] = "RUN"
if self.state is State.FEEDING:
if int(case["feed"]) > int(self.spec["timeouts"]["feed"]): self.fail("feed-timeout")
else:
self.outputs.update(feeder="STOP", conveyor="RUN"); self.move(State.TRANSPORTING, "feed-ack")
if self.state is State.TRANSPORTING:
if int(case["transport"]) > int(self.spec["timeouts"]["transport"]): self.fail("sensor-timeout")
else:
self.outputs["conveyor"] = "STOP"; self.move(State.INSPECTING, "sensor-and-stop-ack")
if self.state is State.INSPECTING:
if bool(case["restart"]): self.fail("restart-mid-cycle")
elif not bool(case["camera"]): self.fail("camera-lost")
elif str(case["label"]) not in self.spec["routes"] or float(case["confidence"]) < float(self.spec["confidence_min"]):
reason = "classification-rejected"; self.safe_stop(); self.move(State.REJECT, reason)
else:
route = str(self.spec["routes"][str(case["label"])]); self.outputs["robot"] = "MOCK_GOAL"
self.move(State.PICKING, "class-transform-route-valid")
if self.state is State.PICKING:
if int(case["robot"]) > int(self.spec["timeouts"]["robot"]): self.fail("robot-timeout")
elif int(case["gripper"]) > int(self.spec["timeouts"]["gripper"]) or not bool(case["grip_ok"]): self.fail("grip-failed")
else:
self.outputs.update(robot="NO_NEW_GOAL", gripper="HOLD_OR_STOP"); self.move(State.PERSISTING, "mock-place-ack")
if self.state is State.PERSISTING:
before = self.ledger_count()
try:
self.db.execute("BEGIN")
if not bool(case["db_ok"]): raise sqlite3.OperationalError("injected-db-failure")
self.db.execute("INSERT INTO ledger(cycle_id,route,status) VALUES(?,?,?)", (cycle, route, "COMPLETE"))
self.db.commit(); self.safe_stop(); self.move(State.COMPLETE, "transaction-committed")
except sqlite3.DatabaseError as exc:
self.db.rollback(); assert self.ledger_count() == before; self.fail(str(exc))
if self.state in (State.COMPLETE, State.REJECT, State.IDEMPOTENT, State.RECOVERY_REQUIRED):
self.safe_stop()
assert self.outputs == SAFE
reason = self.log[-1].split(":", 1)[1] if self.log else reason
return Outcome(str(case["acceptance_id"]), str(case["id"]), cycle, self.state.value,
reason, route, dict(self.outputs), self.ledger_count(), list(self.log))
def recover(self, operator_ack: bool, cause_removed: bool, reconciled: bool, area_clear: bool) -> bool:
if self.state is not State.RECOVERY_REQUIRED or not all((operator_ack,cause_removed,reconciled,area_clear)):
return False
self.safe_stop(); self.move(State.IDLE, "manual-recovery"); return True
def main() -> int:
p = argparse.ArgumentParser(); p.add_argument("--spec", type=Path, required=True); p.add_argument("--out", type=Path, required=True)
args = p.parse_args(); spec = json.loads(args.spec.read_text(encoding="utf-8")); cell = ReferenceCell(spec)
results: list[Outcome] = []
checks: dict[str, bool] = {"AC-01": spec.get("mode") == "simulation-only" and no_secret_keys(spec)}
for case in spec["cases"]:
before = cell.ledger_count(); result = cell.run(case); results.append(result)
acceptance_id = str(case["acceptance_id"])
case_pass = result.state == str(case["expected"]) and result.outputs == SAFE
if acceptance_id == "AC-02":
case_pass = case_pass and result.ledger_rows == before + 1
if acceptance_id in ("AC-04", "AC-09"):
case_pass = case_pass and result.ledger_rows == before
if acceptance_id in ("AC-03", "AC-06"):
case_pass = case_pass and not any("->PICKING:" in entry for entry in result.transitions)
checks[acceptance_id] = checks.get(acceptance_id, True) and case_pass
if result.state == "RECOVERY_REQUIRED":
checks["AC-11"] = checks.get("AC-11", True) and not cell.recover(False, True, True, True) and cell.recover(True, True, True, True)
checks["AC-12"] = all(r.cycle_id and r.transitions and r.reason for r in results) and no_secret_keys([asdict(r) for r in results])
for required in (f"AC-{number:02d}" for number in range(1, 13)):
checks.setdefault(required, False)
evidence = {"checks": checks, "outcomes": [asdict(r) for r in results]}
args.out.write_text(json.dumps(evidence, ensure_ascii=False, indent=2), encoding="utf-8")
for key in sorted(checks): print(f"{key}: {'PASS' if checks[key] else 'FAIL'}")
if not all(checks.values()): return 1
print("ALL ACCEPTANCE TESTS PASS (simulation only)")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Очікуваний результат: AC-01…AC-12 мають PASS; останній рядок — ALL ACCEPTANCE TESTS PASS (simulation only).
Критерій правильності: runner повертає 0 лише коли всі 12 checks істинні; AC-09 доводить rollback, AC-04 не збільшує ledger.
Якщо результат не отримано: release заблокований; виправити перший failing AC і повторити весь набір без зміни expected.
Крок 4. Запуск, evidence і release manifest
python lab24_acceptance.py --spec lab24-spec.json --out lab24-evidence.json
python -m json.tool lab24-evidence.json
Створіть manifest:
{
"project":"MERC-I5 final cell mock",
"revision":"<commit-sha-or-archive-checksum>",
"mode":"simulation-only",
"acceptance_evidence":"lab24-evidence.json",
"hardware_tested":false
}
Очікуваний результат: evidence JSON і manifest однозначно пов'язують код, spec і результати.
Критерій правильності: checksum/SHA не порожній у фінальній здачі; hardware_tested=false.
Якщо результат не отримано: не захищати release як відтворюваний; зафіксувати точну ревізію.
Фізичне acceptance: еталон і адресні прогалини
Фізичний етап мав би додати перевірені адаптери, E-Stop і safe-state tests, координатні/колізійні межі, масу/грипер, виміряні тайм-аути, поетапний запуск без вантажу, окреме схвалення кожного маршруту, recovery drills і журнал. Software PASS є лише передумовою.
Сценарії перевірки
Сценарії перевірки
| Тип | Acceptance cases | Що доводять |
|---|---|---|
| Позитивний | AC-02 normal cycle | повний mock-потік, один commit і safe terminal outputs |
| Позитивний | AC-04 duplicate completed cycle | ідемпотентна відповідь без другого виконання |
| Негативний | AC-03 UNKNOWN, AC-06 camera lost, AC-09 DB failure | reject без robot goal, fail-safe stop і rollback |
| Негативний | AC-05 feeder timeout, AC-07 robot timeout, AC-08 sensor timeout | локальні тайм-аути й перехід у RECOVERY_REQUIRED |
| Граничний | затримка дорівнює timeout і timeout + 1 | однозначна включна межа приймання |
| Граничний | AC-10 restart та AC-11 неповні recovery gates | заборона automatic resume й ручне відновлення |
Таблиця результатів і метрик
| AC | Case | Expected | Фактичний стан | Safe outputs | Ledger delta | PASS/FAIL |
|---|---|---|---|---|---|---|
| 02 | normal | COMPLETE | +1 | |||
| 02 | boundary-timeouts | COMPLETE | +1 | |||
| 03 | unknown | REJECT | 0 | |||
| 04 | duplicate | IDEMPOTENT | 0 | |||
| 05 | feed timeout | RECOVERY_REQUIRED | 0 | |||
| 06 | camera lost | RECOVERY_REQUIRED | 0 | |||
| 07 | robot timeout | RECOVERY_REQUIRED | 0 | |||
| 08 | sensor timeout | RECOVERY_REQUIRED | 0 | |||
| 09 | DB failure | RECOVERY_REQUIRED | 0 | |||
| 10 | restart | RECOVERY_REQUIRED | 0 | |||
| 11 | recovery gates | blocked/IDLE | 0 |
Додаткові метрики: acceptance pass rate (для release 100%), success/reject/fault rate, false-safe count (0), duplicate executions (0), rollback violations (0), mean/max logical cycle duration, audit completeness (100%). Фізичні throughput і latency не підміняти mock-значеннями.
Вимоги до звіту
Див. загальні вимоги. Команда додатково подає:
- scope, ролі й таблицю внесків із commit IDs;
- architecture/interface/state/invariant decisions;
- spec, код, fixtures, evidence JSON і release manifest;
- AC-01…AC-12 із посиланням на доказ;
- fault/recovery analysis та метрики;
- перелік неперевірених фізичних властивостей;
- коротку індивідуальну відповідь кожного учасника про свій модуль і safe state.
Критерії оцінювання
| Складова | Частка | Що оцінюється |
|---|---|---|
| Підготовка | 10% | scope, ролі, контракти, safety/release plan |
| Реалізація | 40% | інтеграційний FSM, adapters, storage, idempotency |
| Перевірка | 25% | AC-01…AC-12, faults, rollback, restart/recovery |
| Аналіз | 15% | метрики, ризики, trade-offs і межі mock |
| Звіт і захист | 10% | evidence, manifest, командні/індивідуальні внески |
| Разом | 100% |
Критична умова: failed safe-state або automatic recovery test блокує позитивне оцінювання реалізації незалежно від середньої суми до виправлення.
Контрольні питання
- Який модуль володіє рішенням про перехід між станами?
- Чому completed cycle ID не можна виконувати повторно?
- Які докази відрізняють software acceptance від physical acceptance?
- Як rollback БД взаємодіє з невідомим фізичним станом?
- Чому автор safety-критичної зміни потребує reviewer?
- Які чотири gates потрібні для recovery?
- Що має містити release manifest?
- Який acceptance test ви додали б для власного варіанта комірки?
Висновки
Команда формулює, які 12 acceptance criteria пройдено, як забезпечені safe state, ідемпотентність, rollback і recovery, які метрики отримано та чому результат залишається mock-прототипом до окремої фізичної апробації.