ZDDC/zddc/internal/handler/default-rsk.form.yaml
ZDDC 3b2280de7f test(handler): coverage for record audit + history flows
Adds history_test.go with eight cases exercising the record-write
orchestration path:
- CreateStampsAuditFields: PUT to a fresh mdl path → audit fields
  injected; response echoes the stamped YAML; no history dir yet.
- UpdateIncrementsRevisionAndArchivesPrior: second PUT archives
  the prior bytes under .history/<base>/<ts>-<sha8>.yaml, bumps
  revision, preserves created_*, chains previous_sha.
- ConflictPreservesHistory: 412 from stale If-Match leaves the live
  file untouched and writes NO history entry (the failed write must
  be a true no-op).
- ClientAuditFieldsStripped: client-supplied created_by / revision
  are silently overwritten by server values — anti-forgery test.
- FilenameMismatch: URL says ...-0002 but body composes to ...-0001
  → 422.
- LockedFieldRejected: posting type=SPC to an rsk row → 422 with
  /type error (rsk/ locks type=RSK via cascade).
- SSRHistoryAtPartyLevel: writes to archive/<party>/ssr.yaml put
  history at archive/<party>/.history/ssr/, NOT at
  archive/.history/<party>/.
- RollupCreate_AssignsRowAndComposesFilename: three POSTs to
  /project/rsk/form.html in two table-scope groups demonstrate the
  server picks up filename_format + row_field+row_scope_fields from
  the cascade, auto-assigns sequence row numbers per group, and
  composes the canonical filename.

Bug fix surfaced by the first test: composeFilename was eliding TWO
separators around an optional placeholder when one was correct.
"ACM-{phase?}-PRJ" with phase="" was producing "ACMPRJ" instead of
"ACM-PRJ". Now drops only the trailing separator from output and
lets the next iteration emit the connector.

Default-project-{mdl,rsk}.form.yaml updated: project-rollup MDL +
RSK schemas gained the six readOnly audit fields and the project-
rsk schema picked up the full table-tracking component shape (+
row) plus an enum-locked type=RSK. The required: list no longer
includes type for rsk schemas — the cascade's field_defaults
injects it after schema validation, and requiring it would 422
well-behaved clients.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 10:08:52 -05:00

182 lines
5.9 KiB
YAML

# Default row schema for a Risk Register entry, served by
# zddc-server when no operator-supplied form.yaml exists at
# archive/<party>/rsk/.
#
# The risk register is structurally different from MDL: the RSK
# TABLE is itself a tracked deliverable (with its own tracking
# number — same shape as an MDL deliverable, type locked to RSK by
# the cascade), and each row in the table is a CHILD of that
# deliverable identified by a per-row sequence (`row`). The row's
# filename = <table-tracking>-<row>.yaml, composed by the server
# from the components below.
#
# Why the table-tracking components live on every row: the row .yaml
# is self-describing — you can pick up any single file and identify
# both the deliverable it contributes to AND its position within
# that deliverable. Multiple RSK tables (different table-tracking
# numbers) can coexist as siblings in the same rsk/ folder; the
# scope-fields shared by their rows are what groups them.
#
# Likelihood and impact use the standard 1-5 ordinal scales;
# severity is also 1-25 (typically L*I) and stored on each row so
# operators can override it when the simple product doesn't capture
# the actual risk profile.
#
# Audit fields are server-managed and read-only (clients must not
# submit values).
#
# To customize: drop your own form.yaml into archive/<party>/rsk/
# (the same directory as table.yaml). Tighten constraints with
# `enum:`, `pattern:`, etc. Add fields and they'll appear in the
# row-edit form; add a matching column to table.yaml to surface
# the field in the table view too.
title: Risk
description: One identified risk. The first eight fields together identify the parent risk-register deliverable; `row` is this entry's position within it. Likelihood and impact use 1-5 ordinals; severity is stored separately so it can be overridden when L*I underrepresents the residual exposure.
schema:
type: object
# `type` is intentionally absent from required: — the cascade's
# field_defaults inject type=RSK after schema validation, and the
# form renderer surfaces it as a locked readOnly field.
required: [originator, project, discipline, sequence, title]
additionalProperties: false
properties:
# --- Table-tracking components: identify which RSK deliverable
# this row belongs to. Together with `row`, they compose the
# row's filename via the cascade's filename_format.
originator:
type: string
title: Originator
description: Organizational unit responsible for this risk register.
minLength: 1
phase:
type: string
title: Phase
description: Optional project phase code (ECI, EPC, ...).
project:
type: string
title: Project
description: Project identifier, or your corporate placeholder for non-project deliverables.
minLength: 1
area:
type: string
title: Area
description: Optional area / budget code.
discipline:
type: string
title: Discipline
description: Engineering or functional group code (EL, ME, CV, PM, ...).
minLength: 1
type:
type: string
title: Document type
description: Locked to RSK by the cascade's field_defaults; the form renders this read-only and the server returns 422 if a different value is submitted.
enum: [RSK]
sequence:
type: string
title: Sequence
description: Zero-padded integer identifying this risk register among the originator's deliverables.
minLength: 1
suffix:
type: string
title: Suffix
description: Optional structural-part suffix on the parent register.
# --- Row sequence within the table. Server-assigned on
# POST-create; preserved as-is on PUT-update.
row:
type: string
title: Row
description: Zero-padded sequence within this risk register (001, 002, ...). Server-assigned on add; do not edit.
minLength: 1
readOnly: true
# --- Risk-level data.
title:
type: string
title: Risk
minLength: 1
category:
type: string
title: Category
description: Free-form grouping (schedule, cost, technical, regulatory, ...).
description:
type: string
title: Description
likelihood:
type: integer
title: Likelihood
description: 1 (rare) to 5 (almost certain).
minimum: 1
maximum: 5
impact:
type: integer
title: Impact
description: 1 (negligible) to 5 (catastrophic).
minimum: 1
maximum: 5
severity:
type: integer
title: Severity
description: Residual risk score. Typically likelihood * impact (1-25), but operators can override.
minimum: 1
maximum: 25
mitigation:
type: string
title: Mitigation
description: Plan for reducing this risk's likelihood or impact.
owner:
type: string
title: Owner
description: Email or party name responsible for tracking this risk.
status:
type: string
title: Status
enum: [open, mitigated, accepted, closed]
dueDate:
type: string
title: Due date
format: date
notes:
type: string
title: Notes
# --- Audit fields (server-managed; read-only).
created_at:
type: string
title: Created
format: date-time
readOnly: true
created_by:
type: string
title: Created by
format: email
readOnly: true
updated_at:
type: string
title: Updated
format: date-time
readOnly: true
updated_by:
type: string
title: Updated by
format: email
readOnly: true
revision:
type: integer
title: Revision
minimum: 1
readOnly: true
previous_sha:
type: string
title: Previous SHA
description: SHA-256 (first 8 hex chars) of the prior revision's bytes.
readOnly: true
ui:
description:
ui:widget: textarea
mitigation:
ui:widget: textarea
notes:
ui:widget: textarea