Skip to content

Issues/Exec/Parts R2: execution document, nonconformance flow, risk & register calibration#56

Merged
CST-100 merged 104 commits into
develfrom
features/issues-exec-parts-r2
Jul 7, 2026
Merged

Issues/Exec/Parts R2: execution document, nonconformance flow, risk & register calibration#56
CST-100 merged 104 commits into
develfrom
features/issues-exec-parts-r2

Conversation

@CST-100

@CST-100 CST-100 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Integration PR bringing the R2 line into devel — the 1.4.0 feature set.

Core features

Demo: Mojave Sphinx (#64, #67)

Kestrel replaced by a demo built from Half Cat Rocketry's HCR-5100 guidebook (GPL, attributed): 182 parts / 197 BOM lines / 7 vendors reconciled to the book, 6 procedures (Vehicle Assembly 11 ops + component builds for ball valves & fin brackets with outputs, GSE, Launch Operations with 20 real signoff hold-points + 2 contingency ops, maintenance), 15 requirements, 17 risks, live demo state incl. a WO held by a real guidebook erratum NC.

Register & traceability passes (live-rehearsal driven)

Security & correctness

State

🤖 Generated with Claude Code

CST-100 and others added 30 commits June 12, 2026 01:54
Master's structure, devel's register: the local optimum the redesign
was hunting. Data renders in tables — detail rows for facts, headed
data-tables for collections. A section's structure follows population:
empty stays the one-line rule (ok.empty_line, reimplemented to emit the
ledger row it always described), populated promotes to the panel
grammar at ledger pitch (panel-ledger: header bar carrying the count
and its flags in the value slot, + ADD at the edge, body a headed
data-table at the parts-list 5px pitch).

- Identity readout: the wrapping mono line becomes a part-identity
  detail table — spelled-out labels (CATEGORY, TRACKING, REORDER…),
  full-ISO tooltips on CREATED/UPDATED/ACTIVATED, absence as a muted
  dash in a row that always renders. DESCRIPTION joins as a row.
- Columns regrouped: left is identity + DESIGN (REQUIREMENTS, TESTS);
  right is STOCK & USE (BOM — moved per amendment, STOCK, PO LINES,
  SUPPLIERS, WHERE USED, PROCEDURE USE, CONSUMED last — the one
  unbounded section). Zone captions are two nouns over the columns; the
  grid is a bounded identity rail plus a flexible collection track the
  wide tables actually spend. BOM regains its NOTES column, dropped
  silently by Amendment 1.
- Stock carries its rung: the on-hand figure renders at 1.125rem/600 in
  the STOCK bar, with a derived LOW flag (coalesce(total, 0) < reorder,
  matching the parts-list derivation) on both the populated bar and the
  empty row. + ADD opens an inline receive micro-form (qty/location/lot
  posting to the inventory API); FULL RECORD remains the long form.
- Contrast floor: --text-muted #666 (3.44:1, below AA at ledger sizes)
  becomes #7a7a7a (4.61:1); interactive elements never render muted —
  the [+] was dimmest exactly when it was the only action. One pitch
  for all ledger rows.
- Labels spelled out (REQUIREMENTS, WHERE USED, PROCEDURE USE,
  RECEIVED); PRINT LABEL returns (the compression rode Amendment 1's
  register pass with no rationale of its own). The tier control
  degrades to a select above five configured tiers — tiers are project
  config, not a fixed trio. The tag macro drops its never-rendered
  activated_at parameter.
- Style guide: the state-dependent section is documented, empty_line is
  the empty-state primitive, id-cell examples marked legacy (rule 7).
  CLAUDE.md: empty-state rule rewritten to name the promotion, plus a
  new layer rule — structure (panels, header bars, headed tables,
  grids) may not be deleted by a register pass; register (pitch,
  accent budget, voice, contrast ladder) is where density lives.

Produced by a 22-agent design review (4 readers, 5 critics, adversarial
verification of every recommendation against the amendment lineage) and
verified by a 21-agent audit (conformance, voice, regression,
consistency); all confirmed findings fixed. 578 tests pass; runtime
smoke on seeded data confirmed all three population states, the receive
flow end-to-end, and LOW derivation parity with the parts list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two gates, not one: raised → UNDISPOSITIONED → (disposition signed) →
DISPOSITIONED → (corrective action done) → CLOSED. The blocking predicate
is `undispositioned`, never `open`; holds are derived from issues at the
commitment moments (COMPLETE/START/SKIP/sign-off/close-out), never stored
on the step.

Schema (a7c41d92e803): containment (step|op|wo|advisory) + containment_step_id,
raised_step_id/raised_by_id, disposition signature (dispositioned_by_id,
dispositioned_at, disposition_rationale), is_condition→actual,
MIL-STD-1520 disposition set, IssueStatus collapses to open|closed,
stored ON_HOLD step rows revert to in_progress.

- core/holds.py: containment semantics §2 — step blocks its COMPLETE (and
  its OP's), bound future step blocks START, op blocks OP COMPLETE,
  wo blocks WO close-out, advisory blocks nothing
- execution API: COMPLETE/SKIP/sign-off/START gated, 400 names the issue;
  auto-completion gated; capture endpoint gains should-be/is, containment,
  assign; ON_HOLD flipping and auto-resume machinery deleted
- issues API: POST /disposition (signature releases holds live, SSE),
  POST /containment (narrowing audited with a note), close rules
  (disposition first; NC close requires corrective action), /holding
- execution UI: ANOMALY capture modal (photo-first, no alert(), toast
  names the ticket), held controls absent + blocker lines, hold chips,
  live release via issue_dispositioned SSE
- issue page §5: HOLDING readout, disposition panel with readiness +
  SIGN (confirm names what it releases), SELF-DISPOSITIONED badge,
  attachments upload, spawn-redline deep link for rework/repair
- issues list §6: IT · TITLE · TYPE · DISP-STATE · HOLDING · PRIORITY ·
  AGE; undispositioned-with-containment sorts first
- MCP §7: raise_issue capture fields, sign_disposition, set_containment,
  get_holds(execution_id)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Three corrections from review on the running build:

- The part page frames like every other OPAL view: the identity head
  (tag, identity table) now lives in a panel window titled PART with
  the actions (PRINT LABEL / EDIT / ACTIVATE / DELETE) in its header
  bar — the app's panel-header-actions grammar, as on the supplier
  page. No more content floating on the page background.
- Sections render their structure always: panel header bar (label,
  count, add affordance) plus the table's column heads, with one muted
  dash row as the body when empty — never a tall box, never narration.
  The one-line empty row is retired from the part page (ok.empty_line
  remains the primitive for index rows outside section panels). The
  empty-state rule in CLAUDE.md and the styleguide page are rewritten
  to match; the LOW flag now derives in one place since the STOCK
  header (figure · REC · LOW) renders in both states.
- Pages restored from the back/forward cache re-sync with the server
  (pageshow + persisted → reload, in the base layout): creating a part
  lands on the part page, and backing out to the list previously
  showed the pre-mutation DOM until a manual refresh — same staleness
  after Ctrl+Z undo.

578 tests pass; live smoke confirmed 9 section panels with dash rows
on an all-empty part, populated tables intact, and the guard served.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the executions OPERATIONS + META tabs with one scrolling
document: live per-step presence (step_claim), a docked MY STEP bar,
issue-bound hold points gating START and COMPLETE (undispositioned
predicate, Issues R2 §9 addendum), authored step images (versioned at
publish), execution captures with attribution, strict_sequence OPs,
and MCP parity tools for agents observing live tests.

- step_claim: claim=START, one user per step, one active step per
  user (supersede), release/complete/skip end claims; zombie claims
  return steps to pending
- issue_step_block: derived holds — active while the issue is
  undispositioned, lift live at disposition (no status choreography);
  fixes the decorative-hold bug (COMPLETE now refuses held scope,
  server-side, and the control renders absent with a blocker line)
- core/execution_flow.py: single home for commitment-moment rules
  (gating, completion, state builder) shared by API routes and MCP;
  also fixes parent-OP resume after disposition (sibling NC count
  included the issue being dispositioned under autoflush=False)
- GET /api/procedure-instances/{id}/state: the controller's document
  as JSON; polled by the page every 5s and identical to the MCP
  get_execution_state payload (MCP runs out-of-process, so the poll
  is the primary live channel; SSE accelerates it)
- web: document landing tab (META/OPERATIONS aliases redirect), OP
  cards with Abby's-rule expansion, one-line step rows with role
  chips/indicators/presence, header roster + progress + meta readout
  popover, rail (holds first, evidence grouped by step, reference
  docs), docked bar with data capture, anomaly capture modal
  (should-be/is, containment, bound steps, photo-first), lightbox,
  tablet drawer via CSS only
- attachments: uploaded_by/note columns, capture + step_image kinds
- authoring: step reference images (snapshot by attachment id into
  both publish implementations), strict_sequence flag
- seed: StepExecution rows now carry parent_step_order

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both 26297a8e0c76 and devel's a7b9c1d3e5f7 revised ccf93c36170a,
leaving two alembic heads after the merge. Chain is now linear:
ccf93c36170a -> a7b9c1d3e5f7 -> 26297a8e0c76.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review on the running build: an empty section's column heads were the
visual noise that master's quiet empty bodies avoided. Empty sections
now keep the panel header bar and go quiet below it — one centered
muted dash row, no thead. Master's narration stays banned; retracting
empty sections entirely was considered and rejected as progressive
disclosure. The empty-state rule and the styleguide page updated to
match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
.execdoc-lightbox sets display:flex, and author display rules override
the UA stylesheet's [hidden] default — so the full-viewport rgba(0,0,0,.88)
overlay rendered on page load and swallowed every click. Add the standard
global guard so the hidden attribute always wins; this also fixes the
JOIN/LEAVE buttons rendering before presence is known (.btn sets display).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- InstanceStatus: pending -> CUT (untouched since cut from the master
  procedure), in_progress -> IN_WORK. Real rename in the enum and data
  (migration b3d1f0a2c4e6), not a display mapping — one vocabulary for
  one fact across UI, API, and MCP. Step statuses unchanged.
- Executions list: WORK ORDER is the leading, linked column; the
  database-id column is gone (voice rule: ids never render in lists).
- Batch cut: InstanceCreate.quantity (1-100) cuts N identical work
  orders in one request, each with its own generated WO number; an
  explicit work_order_number requires quantity 1. The create endpoint
  loops the full per-instance build (steps, BUILD as-built allocation,
  audit log); draft-part validation hoisted ahead of the loop.
- Cut form: quantity field, CUT verb, redirect to the list for batches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stylesheet/script links carried no version, so browsers kept serving
stale CSS/JS on normal reloads — shop-floor pages stay open for days,
which made the lightbox fix invisible to an already-open browser.
static_url() appends the file's mtime; URLs change exactly when files do.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…g, glyph purge

Disposition state is scoped to containment-bearing issues: advisory issues
carry plain open|closed, close freely, and cannot sign a disposition (API +
MCP). The STATE filter grows to four values (open = advisory-open) across
API, MCP, and the web list, with ?state= deep links.

Issues list recolumned to IT·TITLE·TYPE·STATE·PRI·AGE: HOLDS column deleted,
plain unboxed types, UNDISP badge only for bearing+undispositioned rows,
content links render primary (row-link-plain global), one-line rows. The
execution issues tab shares the same register via the new ok.issue_state
macro. Dashboard gains a red "undispositioned holds: N" line.

Detail page: header badge only when bearing, glyphless state words, holds
line reads scope+verb ("OP 4 COMPLETE") from level-aware scope_label,
disposition panel renders only where a disposition exists or is owed, CLOSE
for advisory-open, SIGN flow matches the baseline-panel pattern with an
inline signature confirm. Attachments collapse to one file input + CAMERA
accumulator (issue page and ANOMALY modal).

Glyph purge across templates/JS, enforced by a pictograph-scan unit test;
register rules added to CLAUDE.md. Issue metrics gain advisory_open so the
disposition buckets partition again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… linking

The issue page opens read-only — every field renders as a fact; a header
EDIT control switches to in-place editors (per-field save on blur) and DONE
returns to view. The disposition box loses the readiness checklist and the
SIGN wording: one DISPOSITION button, grayed until type and rationale are
filled (the tooltip names what is missing), with the inline confirm keeping
the release consequence.

Linking now goes both directions: in edit mode the LINKS panel attaches a
work order (EXECUTION select) and a containment boundary step (BOUNDARY
select over that WO's steps), so containment is settable from the issue
side, not only from execution.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… state, layout order

The disposition draft (type, rationale, button) is an edit like any other —
view mode renders the draft as facts. Disabled buttons now gray out
(.btn:disabled had no styling, so the gate was functional but invisible).
DESCRIPTION moves below the SHOULD BE / IS pair.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- step_claim table/model/endpoints/MCP tools are gone (branch never
  shipped, so the infrastructure is removed, not deprecated). step_focus
  replaces it: one row per (instance, user), upserted as the cursor
  moves, no audit events — plus first_focused_at soft telemetry on
  step_execution (never rendered as state).
- Events are exactly COMPLETE / SKIP / ISSUE. The structural gates
  (bound holds, parent holds, strict_sequence, OP dependencies, open
  redlines) moved from START to COMPLETE — a hold cannot physically
  prevent starting work, so it no longer pretends to. SKIP keeps only
  the held-scope gate.
- cut -> in_work flips on the first COMPLETE/SKIP (no separate start
  moment); NC-resume returns steps to PENDING since nothing produces
  IN_PROGRESS anymore.
- POST /{id}/focus + focus_step MCP tool broadcast cursor position
  (cursor_moved SSE event replaces step_started); /leave drops the
  cursor; state payload carries cursors[] per step (several users may
  focus one step) and a focus-based roster.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…purge

- Step rows carry state + indicators only — a 21-step document renders
  zero buttons outside the docked bar. All controls moved to the bar,
  which renders the FOCUSED step: [notes] [ATTACH] [ISSUE] [COMPLETE]
  (COMPLETE primary/rightmost, ISSUE renamed from ANOMALY), SKIP /
  CONSUME PARTS / + REDLINE in the overflow, SIGN OFF replaces COMPLETE
  when the step awaits it. _mystep.html is gone; /dockbar?step=N serves
  the bar for any step.
- Presence: arrow keys and j/k move the cursor row-to-row, click
  focuses; moves POST /focus (debounced) and record no event. Other
  users' cursors render as bordered initials chips in the right gutter,
  outside the OP card border; your own chip renders inverted. Header
  shows "N ONLINE" with a tap/hover roster popover (the only roster
  when rails hide); opening the document joins, pagehide leaves.
- Layout: the document centers at 860px with page-level scroll (inner
  scrollbars gone); the rails float in the gutters — LEFT: online
  roster + OP minimap (click jumps), RIGHT: issues/holds, attachments,
  reference docs. Rails hide below 1280px; the drawer keeps the right
  rail reachable.
- Glyph purge (Amendment 1.1): state is words in state colors +
  typography, never symbols. Complete rows show dim initials+time (no
  mark), holds read "HOLD · IT-xxxxx" in warning color, "HELD BY" on OP
  heads, indicators read "2 ATT · 1 NOTE" and vanish at zero, progress
  drops the check (green carries it). Repo-wide template sweep for
  U+2600-27BF / U+1F000+ and the named set passes with zero hits —
  including pre-existing glyphs on the dashboard, parts ledger,
  requirements queue, and risk pages.
- OP cards: ~2x inter-card margin; OP descriptions and step titles gain
  one type-scale step and one contrast tier.
- Presence heartbeat: the 5s state poll and focus moves touch
  User.last_seen_at (throttled) — roster/cursor staleness was
  previously permanent because nothing in the web path updated it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s R2

Issue keeps raised_step/containment_step and takes the risk module's
risk_links (the one-to-one risk relationship is gone). Alembic gets a merge
revision joining the issues-R2 and risk-scenarios heads. The risk templates
pass the new pictograph scan (REVIEWED stamp and blocker lines drop their
glyphs, test updated accordingly).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-00b96d

Issues R2: nonconformance, containment, and disposition
Claim-era tests map to their focus equivalents: focus places/moves a
cursor without touching step status, two users share a step without
conflict, /leave clears the cursor, COMPLETE acts directly on pending
steps by any user, bound holds and strict_sequence and OP dependencies
gate COMPLETE (not start), NC-resume pops steps to PENDING, and the
first COMPLETE or SKIP flips the WO out of CUT. Document-web tests now
carry the Amendment 1 exit criteria: zero buttons in the exec-doc
column, dockbar serves ?step=N and follows the cursor, indicators and
holds render as words.

Also fixes a real bug the new dockbar tests caught: the partial routes
(/dockbar, /step-row, /rail) rendered with inst_status undefined —
every dockbar refetch dropped the ISSUE/COMPLETE/SKIP controls.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ibration

The risk page opens read-only — scenario fields, scores, owner, narrative,
and links render as facts; EDIT switches to the in-place editors (lint
overlays, selects, link/spawn controls, disposition transitions) and DONE
returns to view, mirroring the issue page.

The ACCEPTANCE READINESS checklist box is gone: one ACCEPT button, grayed
until every hard check passes, with the tooltip naming what is missing
(readiness stays the single source for the gate). The inline accept-confirm
keeps the signature consequence. Once signed, the facts live in the rail
(ACCEPTED · RATIONALE) — the panel's duplicate signature block is deleted.

Register rows drop the per-row disposition badge: REALIZED keeps warning
weight, CLOSED renders muted, everything else calm plain text. Linked-issue
rows reuse the ok.issue_state register and plain mono roles.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Emptiness is information only where content is expected. Declared
intent decides where it's expected; data always renders.

- Part gains procurement: make | buy | both (String(10), indexed,
  server_default make). Creation default: BUY when an external PN is
  provided, else MAKE — applied identically at the API create, JSON/CSV
  import, MCP helper, and seed; explicit values override everywhere.
  Migration b3c5d7e9f1a2 backfills by the same heuristic (empty-string
  external_pn counts as absent). Onshape sync deliberately stays at
  default make: its external_pn is a CAD part number, not a vendor PN
  (documented at the creation site; the backfill asymmetry for
  pre-existing synced parts is noted in the migration docstring).
- Section relevance on the part page: BOM renders for make|both;
  SUPPLIERS and PO LINES for buy|both; STOCK, WHERE USED, PROCEDURE
  USE, CONSUMED, REQUIREMENTS, TESTS always. An irrelevant empty
  section is absent — not collapsed, absent. DATA WINS: any section
  with rows renders regardless of the declaration.
- Empty-state fix: an empty relevant section is ONE line — "LABEL —
  none · + ADD" (ok.empty_line, with an optional flag slot so STOCK
  can carry · LOW) — never a header bar over an empty box. The boxed
  dash placeholders and the ledger-row CSS family are deleted.
- The form gains a PROCUREMENT select in the meta grid (editable in
  draft and active edits); untouched it is omitted so the server
  heuristic stays the one home, with a live preview that follows the
  EXTERNAL REF input in the create flow (external_pn is trimmed on
  submit so the preview and the server agree). PROCUREMENT joins the
  identity table; PartResponse, MCP get_part/create_part echoes, and
  the parts CSV export expose the declaration; the MCP helper
  validates it through the enum and the bulk_create_parts item schema
  declares it (parity with create_part). CSV import parses and
  validates a procurement column.
- CLAUDE.md empty-state rule rewritten around relevance; the
  styleguide page documents the one-line empty and the relevance
  doctrine. The manual's part-creation field table gains the row.

Exit criteria verified live on seeded data: a buy part renders no BOM
section and "SUPPLIERS — none" as one line with + ADD; a make part
renders no SUPPLIERS or PO LINES; a BOM edge forced onto a buy part
renders the section as a populated panel (data wins); every empty
section on any part page is exactly one line. Backfill split the seed
28 buy / 27 make. Migration upgrade/backfill/downgrade tested against
a scratch DB at the prior head.

Audited by a 22-agent workflow (spec conformance, regression, voice,
consistency) with adversarial verification; all confirmed findings
fixed. 681 tests pass. Branch merged with features/issues-exec-parts-r2
(its base) before implementation so the heuristic builds on the
config-derived tier semantics from PR #46.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR #49 (issues R2) adopted ok.empty_line per the empty-state rule while
Amendment 6 changed the macro's grammar and signature in parallel. The
macro stays the one home: issue callers drop the retired action_text
positional (the affordance is always + ADD), the holds test asserts the
new span markup, and the issues-R2 glyph purge (no pictographs; words
carry state) is applied to the new part-page partials (EDIT for the
pencil, YES for the preferred check). The procurement migration
re-parents onto the merged head 52973e130a6f, keeping a single head.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both rails get the bordered-panel treatment; the docked bar sits above
the status footer (offset measured from the footer's real height, set
as --opal-footer-h on init/resize); the bottom scroll reserve drops
7rem -> 5.5rem, which was painting a phantom band under the document.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Keyboard parity with row click: Enter toggles the focused step's body
(instructions, kit, data readout) without leaving the keyboard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gned

- Expanding a step (click/Enter) reveals its full control surface:
  editable data fields, notes, ATTACH / ISSUE / SKIP / CONSUME PARTS /
  COMPLETE (SIGN OFF when awaiting). Collapsed rows stay state-only —
  the zero-buttons criterion now reads "no controls outside step
  bodies" and the web test parses the column to enforce it.
- The docked bar is hidden by default; the header BAR button toggles it
  (persisted). completeStep reads capture fields from whichever surface
  invoked it (step body or bar).
- The bar aligns to the document column instead of the viewport center
  and rides a step above the status footer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- skip refuses SIGNED_OFF steps (was silently overwriting the sign-off
  record) and both skip and sign-off now require an active instance —
  aborted/completed WOs no longer accept step events.
- PATCH /{id} status is no longer freeform: status derives from events;
  the only settable transition is active -> aborted (COMPLETED -> CUT
  reverts and re-emittable completions are gone).
- skip emits instance_started when it is the WO's first event, matching
  complete.
- Concurrent COMPLETE of one step within a poll window: the status flip
  is now a guarded UPDATE (precondition re-asserted at the write), so
  the loser gets "Step already completed" instead of double-writing
  completed_by and re-firing instance completion.
- focus_step uses an atomic upsert — two tabs racing the same user's
  first focus no longer 500 on the unique constraint.
- A contingency step ON_HOLD blocks instance auto-completion (was
  completing around an open NC on a touched contingency).
- touch_user_presence documents its commit constraint; stale "cannot
  START" wording in NC/bind docstrings now says COMPLETE.

Findings from the core-flow and api-routes review dimensions
(wf_d20174ec-dd0); remaining dimensions re-run post-reconciliation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…h Issues R2

The seam swap, as coordinated — this branch landed second, so it adopts
R2's issue ontology wholesale:

- core/holds.py is the single home of containment-derived blocking;
  execution_flow consumes get_hold_state (held_scope_blockers wraps
  blockers_for_complete + boundary holds; the old IssueStatus-based
  predicates are gone with the enum members they filtered on).
- issue_step_block never ships: R2's single containment boundary
  (containment_step_id, "resolve by") replaces the many-to-many binding.
  bind_issue_hold (MCP) now moves the boundary; the ISSUE modal offers
  the full STEP / OP / WO / ADVISORY ladder plus one RESOLVE BY select;
  the NC endpoint takes containment_step_number and writes R2 fields
  (raised_step_id, actual, raised_by_id).
- Stored ON_HOLD choreography dissolves: NC capture no longer mutates
  step status, maybe_resume_step_after_nc_update is deleted — holds
  derive and lift live at the disposition signature, per both specs.
- Containment scopes per R2's table: step containment holds the raised
  step's and its OP's COMPLETE (boundary holds the bound step's);
  op containment holds only the OP's COMPLETE — sub-steps keep
  completing; wo containment holds instance close-out
  (check_instance_completion gains the wo_blocked gate).
- Migration 26297a8e0c76 re-parents onto R2's merge-migration
  52973e130a6f and drops the issue_step_block table; chain is linear
  and round-trips.
- Execution tests reconciled: R2 semantics on focus-model mechanics
  (no /start; derived holds; _sign releases live; sibling work
  continues under step containment).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The integration branch gained PR #48 (part page, Amendment 6) while the
R2 reconciliation was landing. One template casualty: _ledger_is.html
was deleted upstream (its glyph purge rode along into the new ledger
structure); the empty_line macro's new signature flows through the
rail's attachments line. Migration heads (exec focus b3d1f0a2c4e6 +
part procurement b3c5d7e9f1a2) joined by merge migration 894e35219894.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Never instantiated; listened for the step_started event that no longer
exists. The execution document's live layer is execdoc.js.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ence-f396a1

Lists, detail rows, and titles stop rendering database ids
CST-100 and others added 2 commits July 6, 2026 14:34
rationale, category, verification_method, tbr_owner_id, and tbr_due clear
on explicit null via model_fields_set (the containment-boundary pattern);
omitted fields stay untouched and non-nullable fields keep the None-skip.
A wrong TBR owner no longer requires toggling tbr off and on.

Closes #30

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@CST-100 CST-100 added this to the 1.4.0 milestone Jul 6, 2026
CST-100 and others added 23 commits July 6, 2026 14:54
The frozen server died at startup on fido2's missing
public_suffix_list.dat — collect_data_files brings the WebAuthn
package data into the bundle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 'Waiting on sub-steps' blocker labeled children by
parent.step_number + '.' + child global order, naming steps that do not
exist (5.6, 5.7 for children displayed 5.1, 5.2). Use step_display —
the same derivation every other blocker in sequence_blockers uses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
skip_blockers kept only held-scope + redline, so an OP header could be
SKIPPED over pending children — a terminal parent stranding live
children, inconsistent with COMPLETE's refusal. Fold the kind='children'
blocker into the SKIP gate alongside redline; strict_sequence and
OP-dependency ordering stay deliberately omitted.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
'workable and not has_children' hid the COMPLETE control for OP rows
entirely — the pre-amendment absent-control pattern, leaving the
children-gate reason without a UI home. Render the standard gated
control instead: complete_control with the op's complete_gate, which
carries the children blocker, so the bar shows an inert COMPLETE with
'Waiting on sub-steps 1.1, 1.2'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The client updater (execdoc.js updateOpProgress) included both 'raised'
and 'bound' holds; the server-rendered data-op-holds included only
raised/containment holds, so a bound 'resolve by' hold flickered in via
the poll and out via any re-render. A bound child hold gates that
child's COMPLETE and holds the OP's completion
(check_instance_completion._row_held folds blockers_for_start), so the
server derivation now folds bound_blocks_by_step into op_holds_by_order
— both surfaces agree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Thrust Structure & Feedline Integration step kit demanded tca_assy
and tank_assy — both in _NO_STOCK and produced by earlier ops of the
SAME procedure. The model has no per-step outputs (ProcedureOutput is
procedure-level; step kits check flat against inventory), so 'consume
the subassembly you just built' is not representable. The ops BUILD
these assemblies; the kit already carries their raw materials — the two
lines are removed. New test pins the invariant: per-part step-kit
demand never exceeds the procedure gather list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kit_relevant gated both BOM and KITTING on kit_items|consumptions|
step_kits, but KITTING is also home to PRODUCTIONS and the FINALIZE
PRODUCTION control — an output-only procedure hid the tab and stranded
WIP productions. Split the declaration: kit_relevant (parts IN) keeps
the BOM tab; material_relevant (parts in OR out — folds productions and
output_items) gates KITTING. One home per predicate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
From the receive rehearsal:
- The receive table led with the raw line id; it now leads with the linked
  internal PN (voice rules), name beside it.
- Locations: a datalist of known stock locations backs every location
  input, and FILL ALL carries the first entered location down to empty
  ones — receiving into an existing location needs no retyping.
- A line with a quantity but no location was SILENTLY dropped from the
  receive payload; it now blocks with a message naming the part. Blank
  quantity still legitimately leaves a line outstanding (partial receive).
- Purchases list: the row is the PO's click target (row onclick, supplier
  renders plain — accent = identifier); PO page title uses the reference,
  not the database id.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PO-0001 line quantities came from BOM totals while shelf quantities came
from kit-demand x3, so ~37 stock records cited a source line that
received a fraction of what the record holds (e.g. 354 on hand vs 4
received). _stock_plan() is now the single derivation: the kit order
bought exactly what the shelf holds. Pack realism is kept where the
catalog declares it — bulk quantities round up to whole packs and pack
prices become per-unit costs; PO-0002's hand-authored spare quantities
stay in packs and expand to stock units on the line. Test pins the
invariant: stock sourced from a PO line (on hand + consumed) never
exceeds what the line received.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
source_po.number IS Purchase.reference ('PO-0001'); prefixing 'PO-'
rendered 'PO-PO-0001' in the SOURCE fact and the history CONTEXT.
Render the identifier verbatim with the purchases/detail.html fallback
register.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Serialized parts were seeded as a single unit record regardless of
demand: the vehicle kit needs 2x each of the fuel bulkhead, interface
ring, and oxidizer bulkhead, and the valve assembly is demanded 3x
across the vehicle and GSE kits (2 + 1) with only 2 units around.
_stock_plan now sizes serialized stock at total kit demand and
_seed_inventory writes one unit-quantity record per unit (serialized =
one OPAL number per physical unit, mirroring the inventory API), with
WO-4's freshly built valve counting as the third sabv unit. Tests pin
both invariants: every published kit line (procedure kit and consuming
step kit) is coverable from on-hand stock, serialized demand is covered
across procedures summed, and serialized records only ever hold 0 or 1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
linkBoundaryStep() bailed on empty value, so the '—' option was a
silent no-op and the server's explicit-null clear (F8 capability,
model_fields_set) had no web path. Selecting '—' now sends
containment_step_id: null and reloads to show the cleared state.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The WO-1/WO-4 consumption loops skipped tracking=='serialized', so
vehicle SN 001 was 'built' while its serialized components (bulkheads,
interface rings, valve assemblies, TCA parts) sat untouched in stock —
genealogy incomplete, and it masked the serialized shortages S2 fixed.
The two loops collapse into _consume_kit(): bulk lines draw down the
shelf record as before; serialized lines consume distinct unit records
created for the build (qty 1 -> 0 with a consumption row each), leaving
the shelf units for the open demand. Test walks every completed WO and
asserts serialized kit lines are fully consumed as unit records; the S2
availability test pins that remaining stock still covers open demand.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
_switch_database/init_database create the demo file before seed_database
commits, so an interrupted first seed left an empty-but-existing demo
file — and every later ENTER DEMO silently opened a blank demo. enter_demo
now decides freshness inside the demo session: no users and no parts
means unseeded, so it seeds; a seeded demo (users exist) never re-seeds.
This self-heals the interrupted case. demo_file_exists() stays for the
settings UI, where file presence (RESUME vs ENTER) is the right signal.
Integration test simulates the trap: touch the demo file, enter_demo,
assert the demo is seeded; the existing resume test pins idempotence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…copy

F2: get_current_step_data returned the first non-terminal step by
step_number — always the level-0 OP header, which the children gate
400s on COMPLETE and which SKIP would commit wholesale. Select the next
actionable LEAF (level>0 or childless op), mirroring the web dockbar's
bar_step pick; Focus/Complete/Skip/Add Note/Log NC all share this one
selection home.

F6: kit availability read required_quantity/quantity and
available_quantity/available, but KitAvailabilityItem sends
quantity_required/quantity_available — rows rendered 0/'?' always. Fix
the field names in an extracted, testable _kit_row_label and surface
the locations' OPAL numbers.

F12 (TUI half): the finalize confirmation said 'execution #<db id>';
lead with the work order number, else procedure name + cut date.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e case

The guard was db.query(Part).first() alone — a configured real instance
(users, workcenters, project config, no parts yet) sailed through, and
seed_database then OVERWROTE the live project_config and inserted the
publicly documented build/kestrel-demo admin. On a LAN-exposed instance
that is a security problem. The CLI now refuses when the database has
ANY of parts, users, workcenters, suppliers, or a saved project config,
names exactly what it found on stderr, and exits 1; opal seed --force
remains for the deliberate case. Integration tests cover refusal (with
and without parts), the untouched-database guarantee, --force, and the
normal fresh-database path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
'182 parts, 4 published procedures' had drifted (the seed now carries 6
procedures) — counted facts about database contents have exactly one
home, the database. The demo card now says what the demo IS (part
catalog, BOM, procedures, live work orders, issues, risks, POs) with no
numbers to drift. No test asserted the old copy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 'already holds COMPLETE of step X' early return left the orphaned-
issue adoption (procedure_instance_id) merely flushed; call_tool's
cleanup closes (rolls back) the session, silently dropping the very
adoption the cross-WO seam exists for. Commit before returning,
mirroring the normal path (_set_containment commits). Test asserts the
handler leaves no open transaction and the adoption is visible.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
From the PO-receive rehearsal: generic hardware (the 1/4-20 threaded
stud, nuts, washers, raw stock) carried tier 1 — FLIGHT, which enforces
lot tracking and auto-serials at receive. Tier 1 now follows the
guidebook's own emphasis: pressure-bearing (bulkheads, tank casing and
bolts' machined counterparts, valves, TCA parts, propellant-path
fittings/hoses/seals) and recovery-critical (chutes, cords, altimeters,
recovery load-path hardware, shear pins). 45 generic COTS parts —
fasteners, raw stock, commodity electrical, consumables — move to
tier 3, renumbered SPX-D-0014..0058 so the part number's tier code
stays truthful. Tier descriptions updated to declare the philosophy.
The lot-gate receive demo keeps its tier-1 lot-tracked purchased parts
(the -238 O-rings of the erratum NC, QD seals, graphite gaskets), and
the backordered PO-0002 O-ring line still exercises the gate on
receive. Test pins the assignments, PN/tier-code coherence, and the
surviving lot-tracked tier-1 stock.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GET /api/procedure-instances serialized every step's notes and each
note's author via lazy loads — the TUI refresh degraded O(steps+notes).
selectinload the chain (and joinedload the procedure), and batch the
page's version lookup into one query. Payload kept intact: the response
model is shared with the detail endpoint and the TUI consumes it, so
trimming bodies is the wrong altitude here — eager loading alone makes
the query count flat (asserted by test).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Search labeled executions 'Execution #<db id>' with the work order
demoted to sublabel. Lead with the work order number (procedure name as
sublabel); WO-less cuts fall back to procedure name + cut date — a
database id never renders.

Also: ruff format pass over the touched files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…es-seed

Demo seed sweep fixes: coherent PO/stock story, serialized kitting, lifecycle & CLI guards, tier calibration
…es-core

R2 sweep fixes: children gate, hold derivations, TUI targeting, N+1, register
@CST-100 CST-100 merged commit 52b2bde into devel Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant