Pulse/docs/release-control/v6/internal/CANONICAL_DEVELOPMENT_PROTOCOL.md
2026-03-20 10:23:29 +00:00

26 KiB

Pulse v6 Canonical Development Protocol

This is the canonical development protocol for the active v6 release profile. The evergreen Pulse control plane now lives in docs/release-control/.

This protocol exists to make the repo constrained instead of interpretive. Future work should be difficult to do in the wrong shape.

Core Rule

Each important concern in Pulse v6 must have:

  1. one canonical truth
  2. one obvious extension path
  3. explicit forbidden paths
  4. completion obligations when that truth changes
  5. at least one executable guardrail where practical

If a subsystem does not satisfy those five properties, it is drift-prone.

V6 Modernization Invariants

Every governed v6 slice inherits these rules, including lanes that already exist:

  1. identify the canonical v6 model for the surface before changing it
  2. route through unified resources when that is the canonical shape
  3. prefer canonical shared types, APIs, and paths over lane-local variants
  4. remove or isolate legacy host-era terminology, adapters, and compatibility shims from primary runtime paths
  5. update subsystem ownership, path policies, and proof routing when runtime ownership moves
  6. record any unavoidable modernization residual explicitly instead of treating local improvement on a legacy path as lane completion

These rules are retrospective. When an existing lane is touched, reviewed, or reopened, judge it against the same modernization bar as new lane work rather than assuming earlier work closed it permanently.

Required Operating Files

For v6 work, agents must treat these startup files as the execution entry point:

  1. docs/release-control/AGENT_VALUES.md
  2. docs/release-control/CONTROL_PLANE.md
  3. docs/release-control/control_plane.json
  4. docs/release-control/v6/SOURCE_OF_TRUTH.md
  5. docs/release-control/v6/CANONICAL_DEVELOPMENT_PROTOCOL.md

Use derived startup commands before escalating into the heavier machine control files:

  1. python3 scripts/release_control/control_plane.py --agent-entrypoint --pretty
  2. python3 scripts/release_control/agent_preflight.py --pretty
  3. python3 scripts/release_control/status_audit.py --pretty
  4. python3 scripts/release_control/status_lookup.py --lane <LANE_ID> --pretty
  5. python3 scripts/release_control/status_lookup.py --assertion <ASSERTION_ID> --pretty
  6. python3 scripts/release_control/status_lookup.py --release-gate <GATE_ID> --pretty
  7. python3 scripts/release_control/status_lookup.py --followup <FOLLOWUP_ID> --pretty
  8. python3 scripts/release_control/status_lookup.py --work-claim <CLAIM_ID> --pretty
  9. python3 scripts/release_control/work_claim.py --kind ... --id ... --summary ... --agent-id ... --pretty
  10. python3 scripts/release_control/subsystem_lookup.py <path> [<path> ...] --pretty

Escalate into these raw machine files only when the current task genuinely needs their full detail:

  1. docs/release-control/v6/status.json
  2. docs/release-control/v6/status.schema.json
  3. docs/release-control/v6/subsystems/registry.json
  4. docs/release-control/v6/subsystems/registry.schema.json
  5. the relevant subsystem contract in docs/release-control/v6/subsystems/

The startup files answer values, active target, profile selection, release priority, and stable operating rules. AGENT_VALUES.md is the values-only layer: it should stay small and principle-led, and prompt-like guidance should stay close to that layer rather than restating the detailed operating manual. python3 scripts/release_control/control_plane.py --agent-entrypoint --pretty is the executable way to print this ordered guidance bundle when discovery or repo entrypoint is not obvious. python3 scripts/release_control/agent_preflight.py --pretty is the first machine check after the control-plane bundle; it keeps the branch and target check visible before any mutation. After reserving a governed claim, rerun python3 scripts/release_control/agent_preflight.py --require-active-claim --agent-id <AGENT_ID> to verify that the claim is actually persisted before editing. That bundle should stay command-first: use status_audit.py --pretty and the status_lookup.py selectors to answer current-state questions before opening raw status.json or registry.json. python3 scripts/release_control/work_claim.py --kind ... --id ... --summary ... --agent-id ... --write is the executable way to reserve a governed work claim once the slice is chosen. In normal user-facing work, that resolution should stay quiet unless it materially changes the plan, the scope boundary, or the blocker story. CONTROL_PLANE.md and control_plane.json own the evergreen governance model and the active target. scripts/release_control/control_plane_audit.py --check is the stale-target guard for that evergreen layer. SOURCE_OF_TRUTH.md owns profile-specific governance, scope, locked decisions, and the readiness-assertion design rules. status.json owns live lane state, lane-to-subsystem ownership, lane completion records, the active readiness assertion catalog, readiness derivation rules, executable proof commands, structured evidence references, typed lane/subsystem decision records, active work claims, and the canonical machine-readable workspace repo catalog. Lane scores reaching target in status.json mean the tracked architecture and governance work for those lanes is at target; they are not, by themselves, approval to cut an RC or promote stable/GA if phase-blocking readiness assertions, open_decisions, or release_gates still remain. When the agent starts from a sibling repo or the shared workspace root, these same files are still authoritative; resolve them from pulse/docs/release-control/v6/ rather than recreating release control in the current repo. Use python3 scripts/release_control/status_audit.py --pretty for the current machine-derived repo/governance, rc_ready, and release_ready summary. Use docs/release-control/v6/HIGH_RISK_RELEASE_VERIFICATION_MATRIX.md as the human runbook for clearing the manual high-risk gates represented in status.json.release_gates. Use python3 scripts/release_control/status_lookup.py for id-scoped lane, assertion, release-gate, followup, and work-claim views instead of printing the whole status audit when only one governed surface matters. status.schema.json owns the machine-readable status contract. subsystems/registry.schema.json owns the machine-readable subsystem registry contract, including explicit shared-runtime ownership declarations for intentionally multi-owner files. The protocol, subsystem registry, and subsystem contracts answer how work must be done. Readiness assertions are not a parallel checklist. They are the release truths written on top of those governed subsystem surfaces; status.json.readiness_assertions declares the active assertion catalog, proof references, and executable proof commands, while this protocol and SOURCE_OF_TRUTH.md define how that layer must behave. If the agent still needs repeated detailed prompt instruction to follow this system, that is a governance-system gap to fix here, not a reason to keep growing prompt prose. Likewise, if the agent keeps narrating setup or governance plumbing instead of using it quietly, that is a behavior-layer gap to fix here rather than a cue to accept process-diary output as normal.

scripts/release_control/status_audit.py --check is the machine audit entry point for validating live lane evidence references, readiness assertion proof linkage, typed decision records, and derived evidence health. It also enforces canonical list ordering inside status.json so repo scope, lanes, readiness assertions, evidence references, and decision timelines do not drift into noisy, hand-arranged variants. scripts/release_control/registry_audit.py --check is the machine audit entry point for validating subsystem ownership, proof routing, registry lane bindings, canonical ordering for unordered registry lists, and path-policy reachability under first-match precedence. scripts/release_control/contract_audit.py --check is the machine audit entry point for validating structured contract metadata, section presence/order, registry/status linkage, explicit cross-subsystem dependency declarations, and canonical path references and shared-boundary declarations inside subsystem contracts.

Subsystem Contracts

Each major subsystem contract must define:

  1. Contract Metadata
  2. Purpose
  3. Canonical Files
  4. Shared Boundaries
  5. Extension Points
  6. Forbidden Paths
  7. Completion Obligations
  8. Current State

Current required subsystem contracts:

  1. docs/release-control/v6/subsystems/alerts.md
  2. docs/release-control/v6/subsystems/api-contracts.md
  3. docs/release-control/v6/subsystems/cloud-paid.md
  4. docs/release-control/v6/subsystems/frontend-primitives.md
  5. docs/release-control/v6/subsystems/monitoring.md
  6. docs/release-control/v6/subsystems/organization-settings.md
  7. docs/release-control/v6/subsystems/patrol-intelligence.md
  8. docs/release-control/v6/subsystems/performance-and-scalability.md
  9. docs/release-control/v6/subsystems/security-privacy.md
  10. docs/release-control/v6/subsystems/unified-resources.md

If a major subsystem is refactored and does not have one of these files, the work is incomplete.

The machine-readable ownership map for those subsystem contracts lives in docs/release-control/v6/subsystems/registry.json. Each contract must also carry structured metadata that binds the markdown file to its registry subsystem id, owning lane in status.json, and exact declared cross-subsystem dependencies implied by its canonical-file and extension-point references. If a runtime file is intentionally owned by multiple subsystems, that overlap must be declared explicitly in registry.json and mirrored in the contract's Shared Boundaries section; accidental overlap is not an allowed registry state. Shared-boundary entries must use the exact registry-derived sentence shape rather than freeform wording.

Task Completion Protocol

Every substantial task must finish by checking these questions:

  1. Did I change a canonical contract? If yes: update the relevant subsystem contract.
  2. Did I change live lane state, lane completion records, coverage-gap records, readiness derivation rules, readiness assertion proof routes, evidence references, or typed operational decision records? If yes: update status.json. This includes lane_followups when a bounded residual becomes concrete enough to name directly without making it a blocker. It also includes coverage_gaps when the current lane map no longer models a durable product surface cleanly enough. It also includes candidate_lanes when the intended lane split, lane addition, or lane expansion shape is clear enough to name explicitly.
  3. Did I change stable governance, scope, evergreen readiness assertions, or lock a new architectural/release decision? If yes: update SOURCE_OF_TRUTH.md.
  4. Did I replace an old path with a new canonical path? If yes: add or tighten a guardrail so the old path cannot silently return.
  5. Did I add a new extension point? If yes: record exactly where future work must attach to it.
  6. Did I leave compatibility code in runtime paths? If yes: either remove it now or explicitly classify it as a boundary-only exception.
  7. Did the user state a durable product truth or change the current priority? If yes: classify it as a readiness assertion, release gate, open decision, or active-target update and record it in the owning control file instead of leaving it as chat. Treat casual phrases about consistency, seamlessness, drift, bypass resistance, or things that "should always be true" as candidate governance input, not only explicit requests to update the control system.
  8. Did the user casually raise a new quality bar or product invariant? If yes: before ending the task, either normalize it into the control plane or explain clearly why existing governance already covers it. 8a. Am I about to narrate canonical setup that the user does not need? If the information does not materially change the next action, blocker state, or scope boundary, keep it internal and continue the work.
  9. Did I learn that the current lane map is hiding a durable product surface? If yes: record a coverage_gaps entry with evidence, an explicit coverage impact deduction, and the intended promotion path instead of burying the discovery inside generic residual text. When the intended destination is already clear, also add or update the matching candidate_lanes record so the lane-expansion plan is typed, machine-readable, and pointed at the owning control-plane target. Once a lane-shaping gap (new-lane, lane-split, or lane-expansion) has a typed candidate_lanes record, move that coverage_gaps entry to planned; do not mark such a gap planned before the matching candidate_lanes record exists. target-update coverage gaps must stay out of candidate_lanes.
  10. Am I stopping because the lane is actually coherent, or only because the first passing check landed? If the result still has obvious same-lane gaps that make it feel partial, fragile, or not realistically shippable, continue the slice or record the remaining blocker/open decision before stopping. 10a. Am I treating support work like product progress? Guardrail-only proof routing, contract wording, registry cleanup, and audit/test ratchets can support a lane, but they do not count as substantive lane movement by themselves. If the lane state is being advanced in status.json, the same slice should normally also change an owned runtime or product surface for that lane unless the remaining lane gap is explicitly governance-only. 10aa. Am I treating score or evidence deltas as the task instead of as diagnostics? Missing evidence items, current-vs-target score, and proof counts are signals that help locate the real same-lane gap. They are not the work item by themselves unless the remaining gap is explicitly governance-only. For ordinary lane work, identify the runtime, product, ownership, or behavior-coherence gap first, then prove that gap is closed. 10ab. Did I check this lane against the canonical v6 model rather than only the local path it already had? Confirm whether the surface should now run through unified resources, canonical shared types and APIs, explicit subsystem ownership, or retired legacy terminology and shims. If the lane still depends on a pre-v6-primary path, do not stop at local cleanup; modernize it or record the remaining modernization gap explicitly. 10ac. Am I preserving legacy internals when only boundary compatibility is justified? Legacy support is allowed only for explicit boundaries such as migrations, upgrades, imports, external contracts, or wire compatibility that still genuinely exist. If I am improving an old internal path just to keep it alive alongside the canonical v6 path, that is drift. Retire it or record the exception explicitly as boundary-only compatibility rather than treating it as normal lane progress.
  11. Is the remaining work still part of this lane? If not, stop cleanly and normalize it into the next target, another lane, or an explicit open decision instead of letting this task expand forever.
  12. If I am recording a bounded residual, do the tracking references point to the same lane and stay genuinely unresolved? Pair lane status and completion.state coherently: complete goes with target-met, bounded-residual goes with partial, and open must not be paired with target-met. partial means measurable progress, so it must not sit at zero score. not-started means zero score and open. blocked remains an open lane below target rather than a residual or complete state. Blocked lanes must declare typed same-lane blocker references to unresolved readiness assertions, release gates, or open decisions. completion.tracking is only for bounded residuals; if the lane is still open or already complete, leave that list empty until the lane reaches its current floor and the remaining work becomes a governed residual. Do not keep a lane in bounded-residual by pointing at already-passed assertions, cleared release gates, or completed targets. Bounded residual tracking must use a lane followup, readiness assertion, release gate, or open decision rather than a broad target reference. Treat lane_followups as active residual records rather than loose backlog notes: each one should stay referenced by the owning lane's bounded-residual completion.tracking. Once a lane followup is no longer active residual work, remove it from lane_followups instead of leaving it behind with a completed status.

This is the minimum update set for canonical work:

  1. implementation
  2. verification
  3. contract update
  4. guardrail/ratchet update when an old path is retired

This minimum update set is a floor. It does not mean the task should stop at the first minimally valid patch when the next obvious same-lane work is still required for a coherent outcome. When a lane reaches its current target but still has intentionally bounded residual work, the same slice must record that residual in status.json.lanes[*].completion with a short rationale and explicit tracking references to the governing lane followup, readiness assertion, release gate, or open decision that owns the follow-up. When feature work fits an existing lane, reopen that lane's completion state or score instead of pretending the map is still complete. Existing lanes are not grandfathered out of the modernization rules. If a previously touched lane still relies on a non-canonical v6 path, lower its claimed completeness and continue from the canonical model rather than treating the earlier local improvement as sufficient. Do not preserve legacy-primary internal flows under the label of compatibility. Legacy support belongs at explicit boundaries only; when the primary internal mechanism is still old-path-first, the lane remains incomplete unless that exception is explicitly governed. Within an active claimed lane, prefer the largest coherent same-surface slice that still has one proof story. Do not break one modernization or canonicalization arc into many tiny residue commits unless there is a real risk boundary, concept boundary, or proof boundary that makes the split necessary. When feature work does not fit any existing lane cleanly, record a coverage_gaps entry so the lane map itself can evolve. When the durable future lane shape is clear, express that promotion path in status.json.candidate_lanes instead of leaving it only in prose. Before starting governed work in a multi-agent session, record a lease-style status.json.work_claims entry for the governed item you are taking and do not pick an item that already has an active claim. Once the slice is chosen, record that claim immediately before deeper investigation, targeted test execution, or code changes on that slice. Prefer scripts/release_control/work_claim.py for that reservation flow rather than hand-editing status.json. Support-only slices are not exempt from this rule: claim the owning lane or the narrower governed item they unblock, and say in the claim summary why the plumbing is necessary for that governed surface. When the slice is mutating and another mutating agent is already active, prefer scripts/release_control/worktree_base.py first so the canonical clean landing worktree for the base branch exists, then prefer scripts/release_control/worktree_claim.py so the claim is reserved and the branch/worktree are isolated in one step. When an isolated mutating slice is finished and the base branch worktree is clean, prefer scripts/release_control/worktree_finish.py to land the scoped commit(s) back onto the base branch worktree instead of leaving manual merge cleanup for later. If you are continuing straight from one governed slice to another, replace the old claim with the new claim in the same status.json edit so the live claim set never passes through a temporary unclaimed window. If you are stopping without finishing the lane, record the remaining same-lane residual in status.json.lanes[*].completion, status.json.lane_followups, status.json.coverage_gaps, or another owning governed surface before you replace or release the claim. Treat a broad lane claim as blocking narrower same-lane blocker or follow-up claims until that broad claim is released or expires. Treat lane completion, lane coverage, and coverage planning as separate derived signals. status_audit.py --pretty may derive a lane-completion score from current_score/target_score, a lane-coverage score from unresolved coverage_gaps, a coverage-planning score from unresolved coverage_gaps that still lack explicit candidate_lanes, and a conservative governed-surface score as the lower of completion and coverage. Treat those scores and any missing evidence counts as diagnostic summaries, not as the task itself. Use them to choose the real remaining gap on the owned surface rather than optimizing directly for score movement or evidence-count closure unless the remaining work is explicitly governance-only. When lane-expansion work is the intended target, that same audit output may also derive a ranked candidate_lane_queue so agents can take the highest impact valid candidate lane first. That same audit output may also derive available_candidate_lane_queue, active-versus-expired work_claims, and claim-conflict records so agents have an executable "do not overlap" surface instead of only prose. Those claim surfaces reduce logical overlap. For lane followups, readiness assertions, release gates, and open decisions, those tracking references must also reference the same lane; unrelated governance objects are not valid residual placeholders.

This protocol is enforced locally at commit time by the canonical completion guard in scripts/release_control/canonical_completion_guard.py and by the governance guardrail tests in internal/repoctl. Local pre-commit governance checks must evaluate staged v6 control-file content rather than unstaged working-tree noise. Executable readiness assertion proofs must also follow the active target phase from docs/release-control/control_plane.json; repo-ready proof runs alone are not sufficient once the active target advances to an RC or GA promotion phase. Local pre-commit must also reject any unstaged working-tree edits for hook-sensitive governance files under docs/release-control/v6/, scripts/release_control/, internal/repoctl/, .husky/pre-commit, and .github/workflows/canonical-governance.yml, because those local checks still execute or structurally read the working-tree versions. Partial staging is one instance of this broader prohibition. Local formatter steps must also stay scoped to staged files so the hook does not mutate unrelated dirty worktree state. When formatting staged Go files, the formatter must operate on staged blobs in the git index rather than rewriting the whole repo or restaging whole files.

For runtime subsystem changes, the same commit must now include:

  1. the matching subsystem contract update
  2. any dependent subsystem contract whose Canonical Files, Shared Boundaries, or Extension Points explicitly reference a touched runtime path
  3. at least one matching verification artifact update

A staged contract file only counts when the staged diff changes a substantive contract section such as Purpose, Canonical Files, Shared Boundaries, Extension Points, Forbidden Paths, Completion Obligations, or Current State. Metadata-only edits are not sufficient completion proof for runtime changes.

Verification artifacts are subsystem-specific. The allowed proof classes are defined in docs/release-control/v6/subsystems/registry.json and may include explicit guardrail files, contract tests, benchmark/SLO/query-plan artifacts, approved test-prefix matches, non-test contract/type files, or same-subsystem tests only when the registry explicitly allows them.

Cross-subsystem contract dependencies are not advisory. If a touched runtime path is named in another subsystem contract's Canonical Files, Shared Boundaries, or Extension Points, the canonical completion guard now requires that dependent contract to be staged in the same slice.

status.json evidence references must use repo-qualified relative paths. Absolute machine-local paths are forbidden.

When a subsystem defines ordered path_policies, each touched runtime file must satisfy the first matching proof policy for that file. The v6 registry requires explicit path-policy coverage for every governed subsystem, and default subsystem verification is no longer a supported governed path. New owned runtime files must therefore be added to a concrete proof route instead of inheriting subsystem-default verification.

Guardrails

Canonical architecture is not considered real until the repo can enforce it.

Preferred guardrail types:

  1. code standards tests that ban deprecated paths
  2. contract tests that lock payload/wire shapes
  3. query-plan and performance tests for hot paths
  4. parity tests during migrations
  5. drift tests for generated or embedded artifacts

Documentation alone is not sufficient when a rule can be made executable.

The canonical completion guard is the default repo-level enforcement point for subsystem contract updates and proof-of-change verification updates. That enforcement must run both in local hooks and in CI; bypassable local-only governance is not sufficient.

How To Extend Pulse

Before adding new behavior, the author must answer:

  1. Which subsystem contract owns this change?
  2. Which canonical files are allowed to carry the truth?
  3. Which extension point should be used?
  4. Which paths are forbidden for this kind of change?
  5. Which tests or guardrails must change with it?

If those answers are not obvious in under a minute, the subsystem still needs architectural hardening.

Use python3 scripts/release_control/subsystem_lookup.py <path> [<path> ...] to ask the repo which subsystem, contract, proof route, live lane context, relevant decision records, and dependent contract-update obligations apply to a file set before editing.

Boundary Rule

Compatibility is allowed only at explicit boundaries:

  1. migration loaders
  2. persisted upgrade readers
  3. temporary API translation layers with a removal plan

Compatibility is not allowed to become the runtime source of truth.

Expected End State

Pulse v6 should be:

  1. canonical by default
  2. extension-oriented instead of patch-oriented
  3. enforced by tests and guardrails
  4. obvious for both humans and agents to continue correctly