docs: add the modification process and contributor intake - #136
rajasimman-madhivanan wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
Graphify reviewed this change.
Worth a look — the grounded gate found no coupling regressions or blocking issues, but 1 advisory finding(s) below merit a look before merge.
Graphify review — findings
Migrates the Protective-Stop modification procedure and its three record templates (Change Request, Impact Analysis, Release Record) from Notion into the repo, and adds the GitHub scaffolding to run it: a Change Request issue form, a PR template, CODEOWNERS, a documented branch-protection config, and a sync-labels.sh entry for the label set. Documents change-0002 as a sequential, opt-in delivery where change-control enforcement stays inert behind a docs/process/enforcement-mode file until flipped from warn to enforce — with the wire-break check as the one always-on exception — and defines the CI checks as verifying that an authorized, complete change record exists in the right order and that the class label matches the diff, while never judging correctness. Removes the placeholder warning in SECURITY.md now that security@polymathrobotics.com is confirmed, and tells maintainers to open change records on behalf of external contributors per a new EXTERNAL_CONTRIBUTIONS.md.
Worth a look
- python3 failure inside pipe silently ignored —
scripts/sync_labels.sh:21· Escalate · medium- agreed by 2 of 2 members but NOT verified (no proof, no reproducing execution) — consensus is not a verdict; needs human review
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 105 functions depend on the 105 functions this change touches.
Health — grade A; no new coupling hotspots.
Verification — 105 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 105 function(s) in the blast radius were not formally verified this run
| | Required approvals | 1 | Class C's second approval is checked by `change-control`, so the rule follows class rather than burdening every change | | ||
| | Dismiss stale approvals on new commits | On | An approval applies to a diff, not a branch name | | ||
| | Require review from Code Owners | On | Requires one of the three authorizers | | ||
| | Required status checks | `pre-commit`, `host-check`, `firmware-build`, `ros2_build`, `pstop_c_build`, `wire-break` | Gate 0 subset; add `change-control` only after enforce mode is authorized | |
There was a problem hiding this comment.
🔴 An admin applying this doc's 'intended settings' literally would require status-check contexts GitHub Actions never reports, blocking every PR merge — a broader version of the already-flagged wire-break gap. firmware-build.yml's jobs post as build_firmware/build_machn, host-check.yml as host_build_and_misra, pstop_c_build.yml as build_and_test, and ros2_build.yml as "ROS 2 jazzy (required)"/"ROS 2 humble (informational)" — none matches the listed names host-check, firmware-build, ros2_build, pstop_c_build. Only pre-commit's job id matches its own name. Fix: list the actual GitHub check-run context string for every required check (the job id or its name: override, including the ROS2 matrix-suffixed name), not the workflow filename.
Extended reasoning...
GitHub required-status-check contexts must equal the check-run name Actions reports: a job's name: override if set, else its job id, with a matrix suffix. firmware-build.yml has no job-level name:, so its two jobs report as build_firmware and build_machn, not firmware-build. host-check.yml's job id is host_build_and_misra, not host-check. pstop_c_build.yml's job id is build_and_test, not pstop_c_build. ros2_build.yml sets name: "ROS 2 ${{matrix.ros}} (${{matrix.tier}})", producing "ROS 2 jazzy (required)" and "ROS 2 humble (informational)"; the workflow's own comment at ros2_build.yml:19-20 says to mark 'ROS 2 jazzy (required)' as the required check, not ros2_build. pre-commit.yml's job id is literally pre-commit, so only that one entry is correct. An administrator who configures branch protection using BRANCH_PROTECTION.md:11's exact strings ends up with four required contexts (plus wire-break) that no workflow run will ever post, so they stay 'Expected' indefinitely and no PR can satisfy required status checks — main becomes unmergeable, not just missing the one wire-break check.
Verification: nit (doc-only, admin-facing guidance; not applied automatically). BRANCH_PROTECTION.md:11 lists required status-check contexts pre-commit, host-check, firmware-build, ros2_build, pstop_c_build, wire-break. GitHub required-status-check contexts must equal the check-run name Actions reports (job name: override with matrix substitution, else job id). Verified against the workflows:… | normal…
| {"name": "status:in-verification", "color": "006b75", "description": "CR in verification"}, | ||
| {"name": "status:merged", "color": "5319e7", "description": "CR merged"}, | ||
| {"name": "status:released", "color": "0e8a16", "description": "CR released"} | ||
| ] |
There was a problem hiding this comment.
🟡 (optional) Maintainers running scripts/sync_labels.sh never get a status:closed label, even though the process requires it. MODIFICATION_PROCEDURE.md:80 and :140 say a CR moves to Closed status tracked by GitHub label, and the issue form's Status dropdown (.github/ISSUE_TEMPLATE/change-request.yml:127) offers Closed as an option, but labels.json only defines 8 status labels (proposed, under-analysis, authorized, rejected, in-implementation, in-verification, merged, released) — status:closed is missing. Fix: add a status:closed entry to labels.json so sync_labels.sh creates it, matching every other status value referenced in the procedure and the issue form.
Extended reasoning...
A maintainer closes a CR per MODIFICATION_PROCEDURE.md:140 ('The CR moves to Closed when it has been merged...'). They try to apply the corresponding GitHub label via the repo's label set (created by scripts/sync_labels.sh from labels.json). No status:closed label exists in labels.json's 16 entries, unlike the other 8 lifecycle statuses which each have a status:* label. The dropdown in change-request.yml:118-127 lists 9 status values (Proposed...Closed) but only 8 have matching labels. Result: CRs cannot be labeled Closed consistently with the other statuses, breaking the label-based status tracking the procedure and PR claims ('16/16 complete') depend on.
Verification: nit. The inconsistency is real and reachable. labels.json defines exactly 8 status labels (lines 10-17): status:proposed, status:under-analysis, status:authorized, status:rejected, status:in-implementation, status:in-verification, status:merged, status:released — no status:closed. Yet MODIFICATION_PROCEDURE.md:80 states "CR status values, tracked by GitHub label: Proposed, Under Analysis,…
There was a problem hiding this comment.
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
Migrates the Protective-Stop Modification Procedure and its three record templates (Impact Analysis, Release Record, Change Request) from Notion into the repo, and turns the Change Request into a GitHub issue form backed by a label set, CODEOWNERS, and PR/issue templates. Adds change-0002's work-breakdown document describing staged, mode-gated CI enforcement — a change-record checker that verifies an authorized, complete impact analysis exists with a class label matching the diff and the named tests, plus an always-on wire-format guard and an advisory (never-gating) coverage bot — all inert until a committed enforcement-mode file flips from warn to enforce. Removes the placeholder warning on the security@polymathrobotics.com contact in SECURITY.md and documents that maintainers own the safety change record for external contributions.
No blocking issues surfaced. 20 lower-confidence candidates did not survive cross-model review.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 105 functions depend on the 105 functions this change touches.
Health — grade A; no new coupling hotspots.
Verification — 105 functions in the blast radius were not formally verified this run (proofs are advisory here).
Health delta baseline: last indexed commit 6f415e0 (diverged from this PR's base — delta is approximate).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 105 function(s) in the blast radius were not formally verified this run
There was a problem hiding this comment.
Beyond the two inline findings from this run, I also checked and ruled out three other candidates: IMPACT_ANALYSIS.md's safety-module question still resolves against the current authoritative docs/safety/SAFETY_REQUIREMENTS.md, not the superseded Notion SRS doc, so it isn't stale. sync_labels.sh exits non-zero on a failed gh label create and is idempotent on rerun (uses --force), so a transient failure doesn't silently corrupt the label set. BRANCH_PROTECTION.md's single-approval setting for Class C is explicitly documented in that same file as pending the not-yet-built change-control check, not an oversight.
Extended reasoning...
This run's only new commit (f6a512a) edits solely the change-record doc's replacement-stack description and does not touch any of the files behind my prior review's two open inline findings (BRANCH_PROTECTION.md's required-status-check names, and the missing status:closed label), which remain unaddressed. This run surfaced two new CONFIRMED findings (change-request.yml referencing labels that don't yet exist at issue-creation time, and a lifecycle/status contradiction in MODIFICATION_PROCEDURE.md around Closed vs Released) that will be posted as inline comments, so a substantive top-level review body restating them is unnecessary. I additionally spent time verifying three candidate issues that the pipeline flagged as ruled out this run, confirmed the reasoning holds on inspection of the actual files, and am reporting only that narrow addendum per the ruled-out exception, without re-litigating or withdrawing any previously posted finding.
Findings marked 🟡 are optional suggestions and need no follow-up push.
| name: Change Request | ||
| description: Request an assessed change to Protective Stop | ||
| title: '[CR] ' | ||
| labels: [change-request, status:proposed] |
There was a problem hiding this comment.
🟡 (optional) Maintainers get Change Requests silently missing their tracking labels if the first CR is filed before an admin manually runs the label sync script. GitHub issue forms only apply labels listed in labels: if they already exist in the repo; they are never auto-created. change-request and status:proposed (line 5) are created only by scripts/sync_labels.sh, which nothing in this diff runs automatically (no workflow calls it). MODIFICATION_PROCEDURE.md:28 defines a CR as an issue carrying the change-request label, so any CR filed pre-sync has no such label and is invisible to that definition and to label-based status tracking. Fix: have a workflow run sync_labels.sh (e.g. on push to main or repo creation) before the issue form goes live, so the labels always pre-exist.
Extended reasoning...
This PR ships .github/ISSUE_TEMPLATE/change-request.yml with labels: [change-request, status:proposed] at line 5. Those two labels, plus 14 others, are defined only in tools/change_control/labels.json and created in the live repo by running scripts/sync_labels.sh. Grep of .github/workflows shows no workflow references sync_labels.sh; it is a manual script gated on 'an authorized repository administrator' per its own header comment. Sequence: PR merges, issue form becomes selectable immediately, but if no admin has yet run scripts/sync_labels.sh (there is no ordering enforcement or reminder anywhere in this diff), a contributor opens a Change Request. GitHub issue forms apply labels from the labels: key only if the named label already exists in the repository; if it does not exist, the label is silently dropped, not created, and the issue is filed with no label at all. That issue is now indistinguishable from a random issue under MODIFICATION_PROCEDURE.md:28's definition ('A GitHub Issue carrying the change-request label. The single entry point for all changes in scope.'), and it will…
Verification: Severity: nit. The mechanism is real and reachable. The issue form declares labels: [change-request, status:proposed] at .github/ISSUE_TEMPLATE/change-request.yml:5. Those labels exist only as data in tools/change_control/labels.json and are created in the live repo solely by scripts/sync_labels.sh, whose body runs gh label create ... --force. That script is manual: its header comment…
| Merge is blocked until review passes and Gate 1 is green. On merge the CR moves to `Merged`. | ||
|
|
||
| ## Step 8 — Close the change | ||
|
|
||
| The CR moves to `Closed` when it has been merged and its Gate 1 evidence is attached. A CR is never closed with outstanding IA actions. Release happens separately, in section 8. |
There was a problem hiding this comment.
🟡 (optional) The lifecycle model this new procedure defines is internally self-contradictory about which status is terminal, which will make maintainers mis-track CRs during release. Step 8 (line 140) moves a CR to Closed right after merge, before any release happens ("Release happens separately, in section 8"). But Section 8 step 9 (line 207) later moves that same CR to Released, and the canonical status order at line 80 - identically repeated in the status dropdown of change-request.yml:119-127 - lists Released before Closed, i.e. the reverse sequence. Fix: make Closed reachable only after Released (or make the two independent, non-sequential labels), and reorder the status list at line 80 and the change-request.yml dropdown to match the real Step 8 / Section 8 order.
Extended reasoning...
A maintainer follows Step 8 literally: once a Class A/B/C change merges and Gate 1 evidence is attached, they set status to Closed and close the GitHub issue (line 138-140). Weeks or months later a release ships that includes this CR. Section 8 step 9 (line 207: "Every CR in the release moves to Released") requires updating that CR's status to Released. But it is already Closed - the terminal status per the canonical order at line 80 and the identical option order in .github/ISSUE_TEMPLATE/change-request.yml:119-127 (Merged, Released, Closed). The maintainer must reopen an already-closed issue just to record a 'later' status that the declared order says should have preceded closing. This is new content this diff introduces (no prior version of this procedure existed in-repo), and it directly undermines the release record's requirement to enumerate every CR's status accurately (templates/RELEASE_RECORD.md section 1).
Verification: nit. Real internal inconsistency in the newly-added process doc, but no runtime/enforcement impact. Step 8 (docs/process/MODIFICATION_PROCEDURE.md:140, heading "Step 8 — Close the change") sets the CR to Closed right after merge + Gate 1 evidence ("Release happens separately, in section 8"), i.e. before any release. Section 8 step 9 (line 207) later moves that same CR to Released at…
davidt315
left a comment
There was a problem hiding this comment.
i'd just take a look at what we're defining as the actual branch/merge protections and update the docs to reflect, or update the branch protections to reflect what you want
| | Allow force pushes | Off | Published history stands | | ||
| | Allow deletions | Off | Preserve the protected baseline | | ||
|
|
||
| The repository reports `main` as protected, but the classic protection-details endpoint returned 404 and no applicable rules were visible. These intended settings must be compared with live settings by an administrator before application. |
There was a problem hiding this comment.
fyi branch protections and required tests are listed here. I set this up as a best guess at what we might want but feel free to request changes
| @@ -0,0 +1,16 @@ | |||
| # Intended Branch Protection | |||
|
|
|||
| These are intended settings for a repository administrator to apply to `main`. This document does not assert that the settings are currently active; protection details were not readable by the implementing account. | |||
There was a problem hiding this comment.
this comment points to the fact that we can control this in pulumi like we do now, but the agent didn't know where to find that information. I'd recommend looking at what is implemented #139 and comparing with github's native branch protection
There was a problem hiding this comment.
likely we can define these in github if this is for later automation / reference by scripts
There was a problem hiding this comment.
im 99% sure we can define these lables in pulumi and then github takes care of it - no need for sync-labels.sh either
davidt315
left a comment
There was a problem hiding this comment.
I think we need to take a look at what we can manage with pulumi settings. even the labels here can be defined and controlled with pulumi - the sync_labels script isn't needed
The existing change-control proposal combined procedure text, repository intake, and several automation systems in one large review. This first replacement adds only the human process and declarative intake surface, so reviewers can assess the rules before any checker or enforcement workflow lands. ## What changed - Migrated the modification procedure, impact-analysis template, release template, and Notion forwarding map. - Added the Change Request issue form, pull-request template, CODEOWNERS, and intended branch-protection settings. - Added external-contribution guidance without requiring contributors to write safety records. - Added the reproducible 16-label definition and synchronization script. - Confirmed security@polymathrobotics.com as the private reporting contact. - Recorded the four-step sequential replacement plan for original PR #120. - Added no workflow, enforcement mode, checker code, wire guard, or coverage automation. ## Safety lifecycle Modification planning, authorization records, configuration management, and release records. Bears on IEC 61508-1:2010 sections 6.2, 7.16, and 7.18, and IEC 61508-3:2010 sections 6.2, 7.8, 7.9, and 7.10. Co-Authored-By: OpenCode <noreply@opencode.ai>
The replacement plan originally created later branches only after each merge. That made unfinished pieces easy to lose over time. This records every branch and dependency now while retaining the rule that each PR is refreshed and reviewed against its final base before merge. ## What changed - Replaced the sequential-after-merge map with four explicit stacked branches. - Recorded each child branch and its parent. - Updated the process-foundation base to merged traceability commit c2dc4cd. - Kept original PR #120 as recovery history until all replacements exist. ## Safety lifecycle Modification planning and configuration management. Bears on IEC 61508-1:2010 sections 6.2 and 7.16, and IEC 61508-3:2010 sections 6.2 and 7.8. Co-Authored-By: OpenCode <noreply@opencode.ai>
f6a512a to
ea30740
Compare
There was a problem hiding this comment.
Graphify reviewed this change.
Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).
Graphify review — findings
No blocking issues surfaced.
Analysis details — impact, health, verification
Impact & health
Graphify review
Impact — 106 functions depend on the 106 functions this change touches.
Health — grade A; no new coupling hotspots.
Verification — 106 functions in the blast radius were not formally verified this run (proofs are advisory here).
Gate & verification
graphify gate
PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.
Advisory (not blocking):
- verification_scope: 106 function(s) in the blast radius were not formally verified this run
There was a problem hiding this comment.
Beyond the inline findings, I also checked two other candidates and ruled them out: BRANCH_PROTECTION.md's "Required approvals: 1" is intentional per the doc's own rationale, which defers the Class C second-approval check to the not-yet-landed change-control checker rather than this PR; and sync_labels.sh's || exit 1 on a failed gh label create stops the loop but is a manual, explicitly non-safety-critical admin script, not a silent data-integrity issue.
Extended reasoning...
This run re-examined two candidate issues beyond the already-posted inline findings. First, BRANCH_PROTECTION.md:8 sets "Required approvals: 1", which could look like it lets Class C safety-critical changes merge with a single approver, but the table's own rationale column states the second-approval requirement for Class C is intended to be enforced by the change-control checker, which this PR's description explicitly says is deliberately deferred to a later child PR in the replacement stack (not included here). That is a documented sequencing choice, not an oversight, so it was ruled out as a bug in this PR. Second, scripts/sync_labels.sh:33 exits 1 inside a python3 | while ... done pipeline when a single gh label create call fails, which stops processing remaining labels; however the script's header comment explicitly disclaims guarding any safety requirement or design unit, and it is a manual admin-invoked tool (not run in CI), so an incomplete label set on failure is a low-severity operational nuisance rather than a safety-relevant silent failure — ruled out as not requiring inline comment.
Separately, this PR still carries an outstanding CHANGES_REQUESTED review from a human reviewer (davidt315, 2026-09-18T19:42:22Z) with inline comments on BRANCH_PROTECTION.md and CODEOWNERS/labels.json. The only two commits in this diff range (3d8542d, ea30740) both predate that review and do not touch any of those three files' content that davidt315 commented on, so that objection remains unaddressed. Combined with the two CONFIRMED inline findings already queued for posting, this is not a PR to approve in this run.
Findings marked 🟡 are optional suggestions and need no follow-up push.
|
|
||
| | Artifact | Affected? | What must change | | ||
| |---|---|---| | ||
| | Safety requirements baseline (state which — see gap G-1) | | | |
There was a problem hiding this comment.
🟡 nit (optional): Analysts filling out the Impact Analysis template are told to pick among multiple safety-requirements baselines, contradicting the same PR's own resolution of that ambiguity. IMPACT_ANALYSIS.md:47 says 'state which — see gap G-1', but MODIFICATION_PROCEDURE.md:331 (added in this same diff) marks G-1 'Resolved' with docs/safety/SAFETY_REQUIREMENTS.md as the sole authoritative baseline. Fix: update the template row to name that single baseline directly instead of pointing to a gap the procedure itself just closed.
Extended reasoning...
An Impact Analyst opens the template and reads row 4 'Safety requirements baseline (state which — see gap G-1)'. They follow the pointer to MODIFICATION_PROCEDURE.md's gap table and find G-1 says 'Resolved... SAFETY_REQUIREMENTS.md is the authoritative safety requirements baseline; Notion SRS_D040 is superseded working notes. None. Retained for the record.' The template's phrasing implies there is still a live choice to make ('state which'), when the procedure added in the very same commit says there is exactly one baseline. Both files are new/changed in this diff, so the inconsistency is introduced here, not pre-existing. It is documentation-only and low severity, but it directly conflicts with the process's own stated resolution of the ambiguity it references.
Verification: nit. IMPACT_ANALYSIS.md:47 has the row | Safety requirements baseline (state which — see gap G-1) |, and MODIFICATION_PROCEDURE.md:331 (same diff) states | G-1 | Resolved. docs/safety/SAFETY_REQUIREMENTS.md is the authoritative safety requirements baseline; Notion SRS_D040 is superseded working notes. | None. Retained for the record. | — |. The template's "state which — see gap G-1"… | nit.…
| | [Change Request Template](https://app.notion.com/p/3d6c0b1ac5fa8167927af2e753be67c5) | [`.github/ISSUE_TEMPLATE/change-request.yml`](../../.github/ISSUE_TEMPLATE/change-request.yml) | | ||
| | [Release Record Template](https://app.notion.com/p/3d6c0b1ac5fa81708a8cff41150d2b86) | [`templates/RELEASE_RECORD.md`](templates/RELEASE_RECORD.md) | | ||
|
|
||
| Other Notion citations `FSM-1`, `FSM-11`, `CM-2`, `CM-5`, and `VTP-8` are not yet migrated. |
There was a problem hiding this comment.
🟡 (optional) Auditors and the maintainer who plans to retire Notion pages once this map is complete get an incomplete forwarding-address list. Line 12 names only FSM-1, FSM-11, CM-2, CM-5, VTP-8 as not-yet-migrated, but MODIFICATION_PROCEDURE.md section 13 (its own companion doc) cites three other Notion-only sources still 'Exists'-status: CS_D060 (Coding Standard), SWA_D049 (High Level Software Design), VTP_D069 (Validation Test Plan). None of the three appear anywhere in this map, migrated or pending. Fix: the map must enumerate every Notion ID referenced by MODIFICATION_PROCEDURE.md's referenced-documents table, not only the four it happened to migrate plus an unrelated ID set.
Extended reasoning...
MODIFICATION_PROCEDURE.md:285-287 cites CS_D060, SWA_D049 and VTP_D069 as existing Notion-only documents the procedure depends on (Coding Standard, High Level Software Design, Validation Test Plan). NOTION_MIGRATION_MAP.md's stated job is to preserve forwarding addresses for exactly these certification-evidence citations before Notion pages retire. Its only 'not yet migrated' list, line 12, is FSM-1, FSM-11, CM-2, CM-5, VTP-8 - a different ID scheme; CM-2 and CM-5 match ones section 13 also cites, but CS_D060/SWA_D049/VTP_D069 are absent entirely, not even flagged pending. Grep of the file for those three strings returns nothing. changes/change-0002-...md says the owner retires Notion pages once this map exists; if the map is treated as the complete inventory (as its own text implies), those three pages could be retired with no forwarding address recorded, silently breaking the traceability chain for the Coding Standard, HLSD and Validation Test Plan citations.
Verification: nit (documentation inconsistency; no runtime/code impact — the whole change set is governance docs). The candidate's cross-reference facts all check out. NOTION_MIGRATION_MAP.md:3 declares the map "preserves forwarding addresses for certification evidence citations," and its sole not-yet-migrated enumeration, line 12, is FSM-1, FSM-11, CM-2, CM-5, VTP-8. Its companion doc… | nit…
|
The procedure itself reads well and I'm fine with it. Agree with David on the rest: labels and the branch protection doc describe things Pulumi already owns for this org Drop those three and link to the Pulumi definitions instead. This is something likely David (or Emerson) can help with directly. Two small ones:
|
What this delivers
The process-foundation replacement for PR #120. It contains only the human modification process and declarative repository intake surface: procedure/templates, issue and PR forms, CODEOWNERS, reproducible labels, intended branch protection, external-contribution guidance, and the confirmed private security contact.
No checker, workflow, enforcement mode, wire guard, or coverage automation is included. Those changes are isolated in child branches so every replacement exists now without bypassing dependency order.
Classification
Class B: process governance and repository intake only; no runtime safety path, protocol, requirement baseline, or enforcement behavior changes.
Review scope
docs/process/MODIFICATION_PROCEDURE.mdand templates: migrated source process.github/ISSUE_TEMPLATE/change-request.yml: 15 fields, five required, defaults to Class C.github/CODEOWNERS: the three verified write-capable authorizerstools/change_control/labels.jsonandscripts/sync_labels.sh: 16 reproducible labelsCONTRIBUTING.md: maintainer-owned change-record path for external contributorsSECURITY.md: removal of the confirmed contact placeholderMigration comparison
The procedure, Impact Analysis, Change Request fields, and Release Record were previously compared against all four Notion sources with no unexplained omissions. This extraction copies those reviewed artifacts byte-for-byte from PR #120; only split-delivery metadata in the change record differs.
Verification
reason,hazards,description,baseline,class; default Class Cpre-commit run --all-files: all hooks passgit diff -- docs/safety .github/workflows: emptyReplacement stack
mainchange-0002-process-foundationchange-0002-wire-breakchange-0002-change-control-warnEach child will be refreshed and reviewed against its final parent before merge. Original PR #120 is retained as closed recovery and review history.