T7.6 - CR-014/D2/D9/D10: the Ready for QA gate, notification only, shipped off
Marlena's ask: QA sees inbound work ahead of time, not after the fact. The rung: 'Ready for QA' sits between In Progress and QC in BOTH ladders (wp-creation-app.js STATUS_ORDER, server/app.py STATUS_ORDER) and in Field View's list - inside the T7.3 transition model, not beside it: entering it from an unreleased state crosses the release gates, and 'Issue' (hold) stays a branch. The dashboard filter and the navigator grouping learned the state from the ladder without their own edits. Who hears (D2): the QA GROUP, a multi-pick of project members on the SOP wizard's team step, stored as account ids at data.sop.project.qaGroupIds. Entering Ready for QA emails that list and nobody else. A rejection emails the owner AND the same list (amended answer), returns the package to In Progress, and REQUIRES a fresh comment - server-enforced on both write paths (the first version accepted any old comment already on the record, which made every rejection after the first one free; the gate now demands a new entry). Accept and reject are real buttons on the release banner; the comment modal enforces its field; qa_ready / qa_rejected / status_changed all land in the audit history. The link (X1): wp_link() now opens THE package - wp-creation-index.html ?project&wp=<id>, which the creator boots directly and login.html?next= round-trips for a signed-out recipient. It previously pointed at the suite root, which is exactly the failure X1 names; assignment mail inherits the fix. Email discipline (D10 + standing rules): ships OFF (the stored setting the admin console already owns; PUT /api/settings is admin-only, 403 for anyone else, and audited). With it off, transitions write outbox rows marked 'skipped' and the sink receives nothing. With it on, the probe runs a REAL SMTP conversation against an in-process capture sink and asserts the count and the exact recipient set. A dead SMTP host leaves a 'failed' outbox row with the error recorded. The SMTP password exists only in the environment. DEVIATION, stated: the task's Do-paragraph asks the email to include location and a scope summary; the done-when list (and CLAUDE.md) says no customer IP in a message body. The done-when wins: bodies carry the WP number, who moved it, and the deep link. A location canary planted on the package is asserted absent from every captured message. If the fuller body is wanted, that is a product call - needs Nick. Found while building, logged not fixed (BL-021): project_sop_team() reads sop.data['project'], a path pushSOP never writes - the critical-reopen email has never actually reached the PM/CM. One-line fix, owned by T9.9. Field View (D9): 'Ready for QA' is carried by TEXT on the card at 390px. Verification (each probe run alone): NEW tests/qa_gate_check.py 40/40. Regressions: hold_check 50/50, pipeline_check 44/44, aggregates_check 16/16, frame_check 39/39, validation_check 83/83. Items: CR-014, D2, D9, D10 (X1, X3 honored) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -194,6 +194,7 @@
|
||||
<label class="radio-pill" data-val="Scheduled"><input type="radio" name="status"><span class="dot"></span>Scheduled</label>
|
||||
<label class="radio-pill" data-val="Issued"><input type="radio" name="status"><span class="dot"></span>Issued</label>
|
||||
<label class="radio-pill" data-val="In Progress"><input type="radio" name="status"><span class="dot"></span>In progress</label>
|
||||
<label class="radio-pill" data-val="Ready for QA"><input type="radio" name="status"><span class="dot"></span>Ready for QA</label>
|
||||
<label class="radio-pill pill-hold" data-val="Issue"><input type="radio" name="status"><span class="dot"></span>Issue (hold)</label>
|
||||
<label class="radio-pill" data-val="QC"><input type="radio" name="status"><span class="dot"></span>QC</label>
|
||||
<label class="radio-pill" data-val="Closed"><input type="radio" name="status"><span class="dot"></span>Closed</label>
|
||||
@@ -501,6 +502,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- QA REJECT MODAL (CR-014). The comment is not optional: a rejection with no
|
||||
reason is the after-the-fact surprise this gate exists to end, and the server
|
||||
refuses the transition without one. -->
|
||||
<div class="modal-overlay" id="qa-reject-modal">
|
||||
<div class="modal">
|
||||
<div class="modal-head"><div class="modal-title">Return to the crew — QA rejection</div><button class="cmt-x" onclick="qaRejectCancel()" title="Cancel">✕</button></div>
|
||||
<div class="modal-body">
|
||||
<div class="notice">The package goes back to <strong>In Progress</strong>. The owner and the QA group are notified, and the comment stays on the package.</div>
|
||||
<div class="field"><label>What needs fixing <span class="req">*</span></label><textarea id="qa-reject-comment" rows="3" placeholder="What QA found — required"></textarea></div>
|
||||
</div>
|
||||
<div class="modal-foot"><button class="btn btn-ghost" onclick="qaRejectCancel()">Cancel</button><button class="btn btn-generate" onclick="qaRejectSubmit()">Reject — back to In Progress</button></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- VIEW SOP MODAL (comment 2) -->
|
||||
<div class="modal-overlay" id="sop-modal">
|
||||
<div class="modal" style="max-width:640px">
|
||||
|
||||
Reference in New Issue
Block a user