> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cowboy.inc/llms.txt
> Use this file to discover all available pages before exploring further.

# CIP-50: Compiled Network Activation Heights

> Dormant consensus upgrades and height boundaries for persistent single-validator networks

**Status:** Draft\
**Type:** Standards Track\
**Category:** Core\
**Created:** 2026-10-29

> [!NOTE]
>
> **Cowboy Cloud and Cowboy Chain.** This CIP specifies the Cowboy protocol. Cowboy Cloud runs on this protocol today. Cowboy Chain, a public network on the same protocol, is under consideration for a future release. Where the two differ, this CIP says so.

## 1. Scope

From the 2026-10-30 persistent-network cut, every consensus-visible core
change MUST be gated by a compiled activation height for each network and
use additive formats. This includes block validity, execution, accepted
formats, governance-path allowlists, metering, receipts and events. An
additive encoding alone does not authorize new consensus behavior below its
gate. This CIP supplies the native-upgrade procedure referenced by
[CIP-12 §7.9](https://docs.cowboy.inc/cips/cip-12-governance.md#79-non-upgradeable-elements); it does not
introduce an on-chain feature registry or change genesis identity.

## 2. Heights and dormant landing

For each gate `g`, the binary MUST define `H(g, chain_id)` independently for:

| Network | Chain id |
| - | -: |
| Canyon | 26,926,905 |
| Mesa | 26,926,906 |
| BigSky | 26,926,903 |
| Cloud | 26,926,904 |
| Local rehearsal | 1 |
| Development rehearsal | 100 |

Ids 1 and 100 are schedulable only for rehearsal. For an unrecognized chain
id, existing gates retain their pre-refactor globally compiled heights;
new gates resolve to `NOT_SCHEDULED`. No unknown id inherits a named network's
new schedule. New gates MUST land at `NOT_SCHEDULED = u64::MAX` for every id.
The sentinel denotes an unscheduled gate, not a finite activation.
For scheduled gates activation is inclusive: `height >= H(g, chain_id)`.
A separate scheduling change sets a finite height for one network at a time.
This CIP schedules nothing. Fixes retain separate gates even when scheduled
together.

The feature gates are identified by their declared prerequisites, not by
release grouping. For **every chain id**, U1's inventory and scheduling checks
MUST pin these inequalities (equal heights are allowed):

* `H(ROUTE_REFUSAL_CONTRACT_ACTIVATION_HEIGHT) <= H(ROUTE_SESSION_LIFETIME_CEILING_ACTIVATION_HEIGHT)` — A1 before E2.
* `H(ROUTE_REFUSAL_CONTRACT_ACTIVATION_HEIGHT) <= H(ROUTE_SESSION_OWNER_CAP_ACTIVATION_HEIGHT)` — A1 before F1.
* `H(ROUTE_SESSION_FINAL_REPORT_CLOSE_ACTIVATION_HEIGHT) <= H(RUNNER_ASSIGNMENT_RELEASE_ACTIVATION_HEIGHT)` — E1 before E3.
* `H(RUNNER_SLOT_CLASSES_ACTIVATION_HEIGHT) <= H(RUNNER_ASSIGNMENT_RELEASE_ACTIVATION_HEIGHT)` — D1 before E3, as required by the merged assignment-release inventory.
* `H(PVM_SIZED_BUILTIN_METERING_ACTIVATION_HEIGHT) <= H(EXECUTION_CYCLE_LIMITS_ACTIVATION_HEIGHT)` — sized-builtin pricing before J1's lane and resume limits.
* `H(OPERATOR_AUTHORITY_ACTIVATION_HEIGHT) <= H(WORKLOAD_OPERATOR_REMOVAL_ACTIVATION_HEIGHT)` — B1 before B2.
* `H(OPERATOR_AUTHORITY_ACTIVATION_HEIGHT) <= H(RUNNER_CORDON_ACTIVATION_HEIGHT)` — B1 before G1 cordon.
* `H(RUNNER_SELECTION_CAPACITY_ACTIVATION_HEIGHT) <= H(RUNNER_CORDON_ACTIVATION_HEIGHT)` — selection capacity (RES) before cordon.
* `H(OPERATOR_AUTHORITY_ACTIVATION_HEIGHT) <= H(ROUTE_SESSION_FORCE_END_ACTIVATION_HEIGHT)` — B1 before G1 force-end.
* `H(OPERATOR_AUTHORITY_ACTIVATION_HEIGHT) <= H(ROOM_SUSPENSION_ACTIVATION_HEIGHT)` — B1 before G2.
* `H(OPERATOR_AUTHORITY_ACTIVATION_HEIGHT) <= H(WORKLOAD_FORCE_TERMINATE_ACTIVATION_HEIGHT)` — B1 before item 28.
* `H(OPERATOR_AUTHORITY_ACTIVATION_HEIGHT) <= H(RUNNER_POOL_MEMBERSHIP_EVENTS_ACTIVATION_HEIGHT)` — B1 before I1.
* `H(INFRASTRUCTURE_FEE_PAYER_ACTIVATION_HEIGHT) <= H(CBFS_WORKLOAD_COMMIT_PAYER_ACTIVATION_HEIGHT)` — C1 before C3.
* `H(INFRASTRUCTURE_FEE_PAYER_ACTIVATION_HEIGHT) <= H(WORKLOAD_PRICING_ACTIVATION_HEIGHT)` — C1 before H1.

The B/C/G/H/I gate names match the
[operator and economics amendment](neocloud-operator-economics.md#activation-and-compatibility);
G1's two allocated gates are pinned independently.

Interpret `NOT_SCHEDULED` as infinity for these checks. A dependent gate
cannot be scheduled while its prerequisite is dormant. Each gate's own
old/new paths and conditional interactions remain explicitly height-selected;
its other inventory prerequisites also apply. Inventory and
scheduling tests MUST reject every inverted prerequisite; implementations
do not need a fallback for an invalid schedule.

Before `PVM_SIZED_BUILTIN_METERING_ACTIVATION_HEIGHT` is scheduled anywhere,
build-directory-dependent frozen-importer gas and in-process registry probe
costs MUST both be resolved, under
[CIP-2's builtin-metering preconditions](https://docs.cowboy.inc/cips/cip-2-offchain-compute.md#j1-block-time-cycle-denomination)
and the merged J1m scope. J1m adds charges to
the base dict/set behavior; reentrancy safety requires a separate fix.
This requirement schedules no height.

At every block below the height, the old rule MUST run byte for byte,
including refusals, state serialization, access charges and receipt/event
content. Thread `(chain_id, execution_height)` through validation, execution
and metering; a node version, stored version row or first boot is not an
activation trigger. A named one-shot activation write, if needed by another
CIP, runs at exactly `height == H`, not on first binary startup. No new
periodic sweep is permitted.

Existing scheduled gate names and values MUST publish unchanged when the
per-network representation is introduced. The inventory and its CI census
MUST cover every gate and chain id, including dormant gates, in gate-name
order. U1's table/predicate refactor MUST preserve existing results, including
the raw baseline `is_active(u64::MAX, u64::MAX) == true` edge. Inventory
classification and publication still treat `NOT_SCHEDULED` as dormant. If a
refactor changes acceptance at that or any other input, it MUST use the
separately reserved `PER_NETWORK_GATES_ACTIVATION_HEIGHT`, itself initially
`NOT_SCHEDULED`; it cannot silently redefine sentinel behavior.

## 3. Published state and clients

`/chain-parameters` MUST publish the chain's activation metadata, including
`activation.heights` with **scheduled gates only**, using the same inventory
as the binary/release artifact. Never publish `NOT_SCHEDULED` as a scheduled
height. A scheduled gate stays in the map before and after activation until
retirement; a retired gate leaves the map. Runtime policy metadata remains
part of the existing activation contract. A dormant landing therefore does
not change the runtime fingerprint; scheduling and retirement do.

Clients MUST choose behavior using that chain's published gate height and
current chain height, never their own release version. They MUST support both
sides before scheduling and refresh chain state when a refusal crosses a
height. A signing limit valid on the dormant network may fail on the active
network. Access reservations serving both sides MUST cover the larger row.
Absence alone cannot distinguish dormant from retired: before retirement,
consumer releases MUST drop the retired gate's old branch and treat its rule
as unconditional. External runners MUST re-pin the runtime fingerprint when
a gate is scheduled or retired.

## 4. Additive compatibility and reachability

New opcodes, error codes, event topics, governance paths and reason slugs MUST
use fresh allocations; numbers and names are never reused, including after
retirement. Preserve historical decoders. Prefer new state rows keyed by the
existing entity id. An optional field added to a strict record MUST be absent
from old rows and omitted on serialization when absent (`skip_serializing_if`),
so decoding and re-encoding an old row preserves its exact bytes. A golden
vector MUST prove that invariant. Update the metered shadow parser and
encoder together; added address encoding is charged only when present.
Lenient capability decoders MUST ignore new capability fields below the gate.

New instructions MUST be refused below their own height at every reachable
entry: block scanning and native execution, including actor host calls,
governance enactment and timers. Governance-path allowlisting is gated at
both proposal submission and enactment. Gate every added admission read,
decode and write, and reserve its charge before work. Selection, signed-tail
validity, mempool/RPC admission, execution and client mirrors MUST use the
same height-selected access schedule.

The frozen `pvm/Lib` SDK and canonical Watchtower Feed cannot be edited to
implement a post-cut gate. Raw `genesis.json` bytes and their identity
handling MUST remain unchanged; boot-time genesis validation may only loosen.
Neither `RULESET_REVISION` nor release policy values are activation switches.
The repository's contribution rules and block-corpus CI MUST enforce the
repo-wide **gated-or-additive rule** after the cut: additive compatibility
preserves historical bytes, and every consensus-visible behavior change
requires its per-network gate as above.

### Guest event-topic reservation

Registering a new topic in `KNOWN_CONSENSUS_EVENT_TOPICS` MUST NOT change guest
emission below that topic's gate. `is_engine_reserved_topic` MUST select entries
by chain id and height: a new exact topic, and any dotted namespace it newly
reserves, is engine-reserved for guests only from its feature's height. A topic
shared by gates becomes guest-reserved at the earliest of them, such as
`workload.removed` at `min(H(B2), H(H1))`. Below the contributing gate, guest
emission of those strings MUST remain byte-identical to the base.

The base predicate checks every registered exact name and its first dotted
namespace without a height input, and guest `emit_event` refuses a match with
Forbidden (topic predicate,
guest host call).
The new allocations covered by the height-selected rule are:

| Newly reserved namespace or exact name | Contributing topic(s) | Guest-reservation gate |
| - | - | - |
| `system.*` namespace | `system.authority.action` | B1 |
| `workload.*` namespace | `workload.removed` | min(H(B2), H(H1)) |
| `workload.*` namespace | `workload.attempt.force_terminated` | Item 28 |
| `workload.*` namespace | `workload.pool_class_tariff.set`, `workload.capacity.billed`, `workload.runway.changed` | H1 |
| `runner.*` namespace | `runner.slot_classes.updated` | D1 |
| `runner.*` namespace | `runner.lifecycle.quarantined`, `runner.lifecycle.financial_stalled` | J2 |
| `runner.*` namespace | `runner.cordon.changed` | G1 cordon |
| `runner.*` namespace | `runner.pool.member_granted`, `runner.pool.member_released` | I1 |
| Exact name only | `NativeSessionCloseCauseRecorded` | E1 |
| Exact name only | `NativeSessionLifetimeCeilingApplied` | E2 |
| Exact name only | `NativeSessionRunnerReleased` | E3 |
| Exact names only | `NativeSessionPrincipalCounted`, `NativeSessionPrincipalReleased` | F1 |
| Exact name only | `NativeSessionForceEnded` | G1 force-end |

Namespace protection MUST derive from active contributing entries, not all
entries compiled into the dormant registry. Thus `runner.*` becomes reserved
at the earliest active contributor among D1, J2, G1 cordon and I1; the same
rule applies to `workload.*` contributors. An inactive topic cannot newly
reserve a namespace by its presence in the registry alone. The six listed
`NativeSession` exact names do not reserve a blanket prefix or wildcard;
other names retain their existing base treatment.

The following new exact topics are already inside base-reserved namespaces:

| Already-reserved namespace | Topic | Engine-emission gate |
| - | - | - |
| `job.*` | `job.assignment.declined` | E3 |
| `governance.*` | `governance.operator_accounts.changed` | B1 |
| `room.*` | `room.suspension.changed` | G2 |
| `timer_config.*` | `timer_config.governed` | B3 |

They need no new guest restriction; existing base names/namespaces remain
reserved at every height. Engine emission still follows its producer's gate,
even where guest reservation already exists. Every implementing PR MUST add
below-gate and at-gate guest-emit tests for its new exact topic and namespace,
including the earliest-active-contributor behavior for shared namespaces.

## 5. Scheduling, recovery and retirement

Let `R_play` be the activation guard's exclusion margin from the play's
entry check through validator restart, in blocks. Let
`R_draft = R_play + CHECKPOINT_BUILD_MARGIN_BLOCKS`, where the added term
covers the checkpoint build between release drafting and the play.
The draft check uses the **larger** `R_draft`; the play check uses `R_play`.
`SCHEDULING_MARGIN_BLOCKS` is the minimum interval from planned deployment
to activation. The deployment guard owner proposes these values from Canyon
deployment timings; this CIP assigns no numerical value. The margin
MUST leave time to deploy, verify and promote an unscheduling revert before
the old height enters the relevant exclusion margin. Scheduling requires
an in-place deployment path, both guard checks and a recovery point, and
clients supporting both rules.

The guard MUST check the target chain at both release drafting and deployment.
A new schedule must satisfy the scheduling margin. Removing or postponing an
existing finite height is allowed only while `old_H > tip + R_draft` at
drafting and `old_H > tip + R_play` at the play; evaluate the
**old** height, not merely the proposed one. Once inside that window the guard
refuses removal/postponement. An already-active height cannot be removed,
moved or rolled back. A halted chain cannot provide ordinary live-tip proof;
its recovery needs the guard's explicit fix-forward procedure.

Before a height, rollback is an unscheduling revert promoted forward through
the release hops under those guard rules. After activation, fix forward;
never restore an older binary onto state that may have executed the new rule.
Recovery uses a tested backup restore, not a requirement to replay from
genesis. Before scheduling, rehearsal MUST compare old/new execution below
the height and cross it with in-flight work, then compare state and receipt
roots when replaying the captured boundary with the new binary.

The old path may be retired only after **every network has activated the gate
and a post-activation backup exists**. Rehearsal ids are not substitutes for
persistent-network activation. Retirement is a reviewed change, including
client branch removal and fingerprint re-pinning; it is not unscheduling.

## 6. Implementation anchors

The baseline is core `734db73a7c712d0efa2d0fa16f17ca891d5a5879`, the initial
`regenesis/neocloud` head. These anchors describe existing behavior, not an
assertion that this draft is already implemented:

* Activation predicate, `is_active`, and gate inventory: existing globally compiled heights.
* RPC activation, `activation_metadata`: currently iterates every compiled gate; this CIP makes the map per-chain and scheduled-only.
* Application, bank-instruction activation checks, and activation census.
* Chain identity, `ChainIdentity`, and network constants, `CANYON_CHAIN_ID`, `MESA_CHAIN_ID`, `BIGSKY_CHAIN_ID`, `CLOUD_CHAIN_ID`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.