Spec Codex Plugin Lifecycle - joeroberts/release-radar GitHub Wiki
- Status: Approved
- Date: 2026-08-27
- Owner approval: Approved in the Codex delivery task on 2026-08-27
Release Radar ships and manages one versioned local Codex plugin named
release-radar. The plugin teaches Codex how to find or initialize the durable
project tracking documentation Release Radar needs and configures Codex to use
the existing Release Radar STDIO MCP server for typed delivery mutations.
The owner manages the plugin from Settings > Connections. Release Radar can install, update, remove, detect modification, and explicitly reinstall its own plugin. A clean managed installation advances automatically after a Release Radar app update. A plugin that was never installed, was removed, or was modified is never silently installed or overwritten.
This feature does not change the accepted runtime mutation path:
Codex
-> Release Radar plugin
-> existing ReleaseRadarAgentTools STDIO MCP server
-> existing signed XPC bridge
-> Release Radar app
-> app-owned SQLite
There is no HTTP server, direct SQLite access, generic command layer, periodic reconciliation framework, or new ticket command surface.
Implementation contract for rr-p6-connector-recovery / issue #99, following
Chief architecture consultation on baseline 6652228c. This is intended behavior,
not a claim of implementation or acceptance. Accepted ADR-001/002 remain unchanged.
- Replace Boolean handshake completion with a one-shot typed result: compatible, incompatible (expected version and optional observed peer version), timeout, transport failure, or invalid protocol response. Preserve failure stage. Legacy reply zero means incompatibility with unknown supported version, not version zero. Only bounded, allowlisted diagnostics reach users; generic XPC failure does not prove signature rejection or app absence. Late/racing callbacks settle once.
- Preserve dispatch certainty independently: a failure before forward is attempted is not submitted; failure after possible handoff remains outcome unknown. Client transport failures use explicit MCP tool errors; successful command-result shapes and existing outcomeUnknown semantics remain intact. Health/recovery never resends an envelope or allocates a replacement request ID.
- Add one bounded, read-only health query on the app-facing broker interface, restricted to the authenticated Release Radar app. Return current wire version, whether this caller connection is the registered app, and the broker's last authenticated tools contact (or none). Advance the bridge wire version for the added selector, preserve bootstrap handshake/register selectors, and fail closed across mixed versions. Do not change commandEnvelopeVersion or reinterpret its unsupportedVersion as a transport mismatch.
- Gate snapshots by time and connection generation. Clear observations on broker restart; mark app state stale on interruption/invalidation; ignore old-generation replies. A fresh round trip proves app-to-bridge readiness only. An authenticated tools handshake proves contact, not a successful delivery action or a healthy Codex session. Another client's success cannot certify the retained client.
- Preserve UID, Team, identifier, anchor and code-sign enforcement. Health is not available to tools or Coordinator peers. Do not accept client-reported rejection, stale or healthy status. Rejected clients may never reach a broker method, so there is no reliable rejection feed; absence of contact must remain unknown. An already-running old helper cannot acquire new diagnostics retroactively.
Follow the Connections card/status/last-observation/action pattern in
docs/design/mockups/settings.png, preserving the existing app composition.
Keep plugin installation, app bridge readiness and connector observations separate:
- Show “App bridge: Available”, “Connection failed” or “Version mismatch”, and separately “Last authenticated connector contact: [time]” or “Not yet observed”. Refresh performs a bounded health read, not a command retry or a second store host.
- Connection failure guidance: “The Release Radar connector could not connect. After updating Release Radar, quit and reopen Codex, then check the connection again.” Explain that existing tasks should first reach a safe stopping point.
- Missing app registration: “Release Radar is not connected to its bridge. Reopen Release Radar, then check again.” Do not falsely promise automatic reconnection.
- Uncertain action: “The result is unknown. Check the action's recorded outcome before taking further action.” No automatic replay.
- Rename Restart helper to Restart plugin helper, explaining that it refreshes plugin management, not the running connector. No supported per-connector restart has been established; do not invent one or kill processes.
No new MCP tool, database schema, telemetry framework, trust exception or lifecycle-helper authority is introduced. Implementation touches only the existing transport, AgentTools, broker, application host, app health plumbing, Settings and focused tests. Preserve other app behavior and unrelated source.
Test typed handshake outcomes, late/racing callbacks and no forward after failed admission; app registration/disconnection, broker restart, stale-generation replies and unknown/no-contact observations; and wrong-signer/UID/identifier rejection, including denial of the app-only health query. Exercise uncertain handoff with zero automatic replay on health refresh/recovery; explicit idempotency replay alone is insufficient evidence for that property.
Retain the original Codex connector across a signed app replacement. Observe its actual compatible or failed/unknown state honestly, verify visible guidance, perform supported owner recovery, then obtain a successful supported inventory and updated contact observation. A fresh shell-launched helper cannot substitute for this acceptance. Include mixed wire versions and app disconnection. Independent review covers trust/recovery and independent UI QA covers normal/narrow/wide layouts, keyboard access and announced failures. Build Agent runs compilation/tests; RO remains read-only. No completion is inferred from this contract.
The signed Release Radar app contains the canonical local marketplace and plugin source. The package contains only the components needed for this integration:
Release Radar marketplace
.agents/plugins/marketplace.json
plugins/release-radar/
.codex-plugin/plugin.json
.mcp.json
skills/release-radar/SKILL.md
The manifest version tracks CFBundleShortVersionString. A deterministic
SHA-256 package digest is calculated over normalized relative paths and file
bytes. The app-bundled package is the intended source; Codex owns its installed
cache, enablement, approvals, configuration, and MCP permission state.
The corrected repository handoff ships app and manifest version 0.1.5
together. A clean managed 0.1.4 plugin reaches it only through the existing
clean-managed launch update or existing explicit Update action; no same-version
overwrite or additional updater path is introduced.
The .mcp.json uses the official direct server map form, with
"release_radar" as the top-level machine key and command/args beneath it.
It does not use a camel-case mcpServers wrapper. The underscore is required
for Codex to form the callable mcp__release_radar__... namespace; it is not a
second plugin, marketplace listing, or user-facing alias. The skill frontmatter
description is the trigger-only sentence Use when working in a repository tracked by Release Radar or when the owner asks to initialize or synchronize Release Radar tracking.
The skill is concise and project-facing. It tells Codex to:
- require the copied prompt to name the exact Release Radar-authorized repository root, canonicalize that root and the current Codex task root, and continue only when they match exactly;
- when the exact root is missing or the task is rooted at a parent, child, or different directory, report the mismatch and stop before any repository write or Release Radar call;
- inspect the current repository instructions and Release Radar tracking artifacts before reporting or changing delivery state;
- when the owner asks to initialize or update tracking guidance, preserve every
unrelated repository instruction and manage
exact versioned Release Radar managed block in the repository-root
AGENTS.md, creating the file when absent, appending when no marker exists, replacing only one older managed range, and stopping on malformed, duplicate, modified-current, or newer markers; - refuse before writing if the root
AGENTS.mdor delivery-ledger path is a symlink or non-regular filesystem item; - preserve the durable delivery ledger at
docs/delivery/progress.md, creating it only when absent with the guidance version and an explicitly pending Release Radar audit, never inventing delivery state; - use the plugin's typed MCP tools for consequential Release Radar state transitions rather than editing Release Radar's SQLite database;
- write the managed block and absent-only pending ledger first and directly read
them back; then record the actual root
AGENTS.mdwith the existing ticketlessrelease_radar_add_evidencemutation and an evidence ID prefixedrelease-radar-handoff:v1:. When Release Radar reports that the exact block is present but this audited evidence is absent, the copied repair prompt authorizes the same evidence mutation without rewriting repository files. Never useupsert_phaseor another delivery-state mutation merely to manufacture a handoff audit; - when the app is closed or no callback is available, retain the repository
guidance with its audit pending, tell the owner to open Release Radar, and
retry the complete evidence request.
outcomeUnknownlikewise replays the complete request verbatim—including evidence ID, path, command, root, reason, attribution, and UUIDrequestID—through the existing idempotent receipt; - after a successful result, replace only the pending audit value in a ledger created by this handoff with the returned audit-event ID and perform final readback. A post-audit ledger failure is repaired without another mutation;
- surface uncertainty instead of fabricating completion or acceptance.
The skill does not grant new authority to mutate project files, Release Radar data, or external systems. Codex performs repository documentation writes only when normal authorization and the owner request allow them. Release Radar alone validates and persists its existing typed MCP mutations; the app and lifecycle helper have no repository-write authority. Normal Codex authorization rules still apply.
For every onboarded project, Release Radar uses the existing security-scoped
project bookmark to inspect only the root AGENTS.md and pairs that read-only
result with its existing ticketless evidence row for the exact file. Release
Radar guidance current requires both the exact versioned block and an
available evidence ID prefixed release-radar-handoff:v1:. An exact block
without that audited evidence is Release Radar guidance handoff incomplete
and offers the copied repair prompt; malformed guidance remains needs
repair. Missing, update available, and unavailable retain their existing
meanings. A symlink or non-regular root instruction path is unavailable. The
guidance version is independent of the app/plugin version.
Inspection occurs during the existing project/dashboard load path, including
the existing refresh after a successful MCP mutation. The app never writes the
repository instruction file: when owner action is needed, it copies the exact
current-task prompt, shows and embeds the exact canonical authorized root, and
Codex performs the authorized managed-block change through the installed skill
only from a task rooted there. No watcher, poller, synchronization framework,
or repository-read MCP operation is introduced.
The managed plugin and marketplace keep the single canonical product identity
release-radar. Its bundled STDIO MCP server uses the machine identifier
release_radar because Codex CLI 0.149.0-alpha.4.3 accepts and lists a
hyphenated plugin server key but does not expose that server's tools to the
model. The underscore is a syntax adaptation inside the one plugin, not a
second durable product alias. Release Radar does not introduce a second plugin
or server name such as release-radar-plugin.
This distinction preserves the original recommendation's outcome: after the
first managed Install completes, there is one installed Release Radar plugin
and one callable bundled MCP server. The temporary coexistence of the legacy
owner-configured release-radar entry and bundled release_radar server is
limited to first-Install verification and rollback.
Some existing installations have a legacy owner-configured MCP entry with the
canonical name. Release Radar recognizes that entry only when supported
codex mcp get release-radar --json output has all of these semantics:
- name
release-radar, enabledtrue, and no disabled reason; - STDIO command
/Applications/ReleaseRadar.app/Contents/Helpers/ReleaseRadarAgentTools; - no arguments, working directory, or non-empty environment.
Additional CLI JSON fields are tolerated only when they do not contradict those required semantics. Malformed, duplicate, or contradictory values fail closed.
An absent entry is also an allowed initial state. Any same-name entry that does not match this contract is a conflict and is never overwritten or removed. All changes use the supported fixed Codex CLI operations; Release Radar never edits Codex configuration directly.
For the corrected feasibility proof, the exact legacy entry remains untouched
while the derived plugin is installed. Before installation, supported
codex mcp get release_radar --json must return the pinned exact-absence tuple:
exit status 1, empty stdout, and stderr
Error: No MCP server named 'release_radar' found. plus one newline. Any
present, malformed, or ambiguous bundled-key state stops before mutation. A
normal fresh Codex task must then prove that the plugin supplies callable tools
through server key release_radar. Cleanup removes only the temporary plugin
and marketplace and verifies exact bundled-key absence plus unchanged legacy
entry and unrelated-state fingerprint. Earlier accepted live evidence already
proves exact legacy removal, pinned absence, and exact restoration through the
fixed supported CLI vectors; it is cumulative migration evidence and is not
rerun.
For the product's first managed Install, Release Radar also recognizes the
owner's troubleshooting-era direct release_radar entry only when it is an
enabled STDIO entry with no disabled reason, the exact packaged AgentTools
command, empty arguments, null working directory, and empty or null environment
fields. Contradictory fields fail closed. The helper classifies both this direct
entry and the legacy release-radar entry before any mutation, then freshly
rereads and removes that exact direct entry through the supported CLI,
then verifies the machine key is absent before installing the plugin. Any
changed or unrecognized release_radar entry stops before mutation. If the
attempt later fails, rollback restores the direct entry only when this operation
removed it and the key remains absent; it never overwrites changed state.
Release Radar separately records the operation-local initial release-radar
observation as absent or exact legacy, installs the managed plugin, and verifies
bundled server release_radar. An initially absent entry
proceeds directly to the final bundled-server postcondition. For an initially
exact legacy entry, Release Radar immediately reads release-radar again and
removes it through the Codex CLI only when that fresh observation still matches
the exact legacy contract, then verifies bundled server release_radar again.
An absent or changed fresh legacy result causes rollback without removal or
restoration. After an attempted removal, restoration is allowed only because
that operation may have removed the recognized entry. Only the final bundled-
server verification permits managedInstalled. Every failed attempt removes
only plugin/marketplace state it introduced and records no success receipt or
audit. Later Update, Reinstall, and Remove operations do not repeat this
migration, and Remove does not recreate the legacy entry. No migration flag,
second plugin/server alias, reconciliation loop, or additional XPC operation is
introduced.
The sandboxed app cannot manage Codex-owned state under ~/.codex. A separate
same-user lifecycle helper is therefore the only additional executable in this
design. It is an installation-management boundary, not an agent runtime
boundary.
The helper:
- is separately signed with Hardened Runtime and remains inside App Sandbox, with only the minimum local exceptions in the fixed sandbox and legacy migration decision;
- has no app-group, Keychain, folder-bookmark, network, SQLite, or
ReleaseRadarCoreaccess; - is registered as a same-user
SMAppServiceagent; - exposes one private authenticated XPC protocol with exactly
status,install,remove, andreinstalloperations; - accepts no caller-selected command, path, plugin identifier, marketplace, environment, arguments, or source URL;
- invokes only the fixed ChatGPT-bundled Codex executable at
/Applications/ChatGPT.app/Contents/Resources/codexafter verifying its path, file mode, signature, identifier, and OpenAI team identity; - invokes the executable directly, never through a shell or
$PATH, with a fixed argument allowlist, sanitized environment, neutral working directory, bounded output, strict JSON parsing, and a finite timeout; - for integrity status only, obtains the effective user's home directory with
getpwuid_r(geteuid()), never$HOME, and combines it only with the CLI-validated target version to derive~/.codex/plugins/cache/release-radar/release-radar/<version>; the version is one path component of at most 128 ASCII characters and must be a strict SemVer value (major.minor.patchwith optional valid prerelease/build identifiers), with no empty identifier,/, NUL,.or..component; - must, before installed-cache integrity can be represented as hardened, open
every fixed directory component descriptor-relatively with no-follow
semantics, recognize only either the historical three-file inventory
(
.codex-plugin/plugin.json,.mcp.json, andskills/release-radar/SKILL.md) or the current four-file Shared Execution V1 inventory (which additionally containsskills/shared-execution/SKILL.md), and ensure each included file remains the same regular file throughout the digest read; - never writes Codex configuration or cache directly and never reads Codex configuration, another plugin, or any other cache path;
- returns normalized status or error categories and never raw command output, home paths, configuration contents, or credentials.
The production status path accepts no filesystem root. The helper-local
PluginDigester anchors the getpwuid_r home with a search-only (O_SEARCH),
no-follow directory open; home is traversed but never enumerated. The signed
helper's existing sandbox grants cache reads below .codex, while an O_RDONLY
home open fails with EPERM. Each fixed cache component and targeted version
retains its read-only directory open separately with
openat. It retains descriptor and parent/name links through the snapshot,
enumerates only within the version root, and rejects symlinks and nonregular
entries. Harmless empty directories remain compatible with the earlier reader;
only the recognized three-file or four-file regular-file inventory is read.
Included files use no-follow, nonblocking opens and are checked against their
entry metadata before reading. Each file is read once, and retained bytes supply
both validation and the unchanged normalized digest. Handle and entry identity,
mode, size, modification/change times and directory entries are checked before
returning. The signed-resource marketplace reader retains its prior behavior.
Native synthetic tests exercise the actual helper reader, both recognized file inventories and frozen digests, symlink/nonregular/FIFO rejection, replacement and same-size mutation races, read errors and path-free failures. Signed sandbox checks must separately establish legitimate traversal-only home access without broader entitlements. Synthetic reader success alone does not prove installed-helper behavior.
The XPC listener accepts only the same effective user and the signed Release Radar application identity. The helper exposes no MCP, STDIO, URL, network, or agent-facing endpoint. The existing bridge, MCP server, dispatcher, and SQLite ownership remain unchanged.
Release Radar persists only the owner's intent and the last successfully verified lifecycle receipt in its app-owned store:
-
neverInstalled: the owner has never installed the plugin; -
managedInstalled: Release Radar successfully installed or reinstalled the plugin and recorded its version and digest; -
removed: the owner explicitly removed the plugin or a previously managed installation is now absent; -
attentionRequired: observed state is modified, inconsistent, unsupported, or could not be verified.
The installed Codex state is observed external state, not a second authority. The UI derives one of these presentation states:
| State | Meaning | Action hierarchy |
|---|---|---|
| Checking | Status is being read | No actions |
| Not installed | No managed installation is present | Install, primary |
| Installed | Installed version and digest match the shipped package | Remove, destructive only |
| Update available | An intact recognized managed version is older | Update, primary; Remove, secondary destructive |
| Modified | Installed content differs from its last managed digest | Reinstall, primary; Remove, secondary destructive |
| Needs repair | Cache, manifest, marketplace, or receipt state is inconsistent | Reinstall, primary; Remove, secondary destructive |
| Failed | Codex/helper/status operation failed | Try Again, primary |
Installed integrity classification is exact. The bundled current package and manifest are version 0.1.16 and use the four-file Shared Execution V1 inventory. The earlier 0.1.5 three-file handoff remains a historical compatible inventory, not the current package shape. Marketplace-manifest validity is checked separately; the package digest covers only plugin-relative inventory files.
-
clean(version:digest:)requires exactly one recognized inventory (the legacy three-file or current four-file shape), stable regular files, valid plugin manifest and MCP JSON, matchingrelease-radaridentity and targeted version, and the recognized digest; -
modified(version:observedDigest:)requires that same valid, stable shape but a different digest. A one-byte skill edit or a syntactically valid changed MCP command is Modified and its bytes are preserved; -
needsRepair(.integrityInvalid)covers an installed CLI target with a missing or extra entry, malformed manifest or MCP JSON, symlink, non-regular entry, rejected version/path escape, partial tree, component replacement, or content that changes while read. It performs no write; -
.integrityUnknownis reserved for an operating-system read failure or a missing recognized digest that prevents classification; it is presented as Failed and performs no write. A targeted CLI result with no installed plugin isabsentand does not trigger any cache read.
Checking and every in-flight operation expose no actions. Their named status announcements are Checking plugin status, Installing plugin, Updating plugin, Removing plugin, Reinstalling plugin, and Trying plugin status again. Completion announces the resulting state; a failure announces privacy-bounded actionable failure copy.
Codex availability, plugin installation, and live Codex observation are separate statuses. An installed plugin must never be represented as proof that Codex is running or connected.
When a supported desktop-observation attachment is unavailable, Settings must say that Codex desktop observation is unavailable (with the bounded reason when known). It must not say Codex unavailable solely because no observation attachment exists. This presentation change does not add observation transport, polling, or a live-state claim.
- Initial install requires a visible owner action.
- Initial install accepts only an absent legacy
release-radarMCP entry or the exact recognized legacy entry above. Bundled serverrelease_radaris verified before and, when migration occurs, after the one-time legacy replacement. An unrecognized legacy entry fails closed. - Remove requires confirmation that it removes the managed Codex plugin and does not remove Release Radar delivery records. Cancel is the default button, returns focus to Remove, and makes no helper call. A confirmed removal announces start and its verified result or privacy-bounded failure.
- Reinstall requires confirmation that it replaces the managed plugin with the shipped version and may overwrite local plugin modifications. Cancel is the default button, returns focus to Reinstall, and makes no helper call. A confirmed reinstall announces start and its verified result or privacy-bounded failure.
- App launch after an app update may update only an intact, recognized,
managedInstalledcopy. - The signed local replacement workflow stops the running app and its two Release Radar-owned Service Management helpers before promoting the new bundle, preventing an older helper from surviving the replacement with stale in-memory package metadata.
-
neverInstalled,removed, absent, modified, conflicted, or unknown state never triggers automatic installation or replacement. - External removal of a previously managed plugin becomes
removed; it is not automatically repaired. - Modified content is preserved until the owner explicitly chooses Reinstall.
- A lifecycle receipt changes only after the helper command succeeds and a fresh status check proves the expected postcondition.
- A timeout, malformed result, conflicting marketplace identity, or contradictory postcondition becomes a visible recoverable failure. It is never silently retried.
- After a successful lifecycle change, Settings tells the owner to start a new Codex task to load the change.
- Restart helper is a non-destructive secondary action beside the plugin controls. It invalidates the app's existing lifecycle-helper connection, unregisters the fixed helper when enabled, registers the helper bundled with the currently installed Release Radar app after macOS confirms the old helper has terminated, and performs one fresh status read. It never invokes Install, Remove, or Reinstall and does not change plugin contents, Codex configuration, or project data. It adds no XPC operation or caller-selected input. Existing observation persistence applies; specifically, an attention-required receipt returns to managed-installed only when the fresh clean version and digest exactly match its last verified managed identity.
- Restart participates in the existing serialized lifecycle operation state. The button remains visible but disabled while any lifecycle operation is in flight; Settings announces Restarting lifecycle helper, then an accessible success stating that the helper restarted and plugin status refreshed, or the existing actionable privacy-bounded failure. A Login Items authorization failure directs the owner to approve the Release Radar helper and retry.
Add the first section under Settings > Connections, using the existing
settings.png visual language. The section is titled Release Radar Codex
Plugin and includes:
- explicit status text and installed/shipped version where known;
- one primary action appropriate to the current state;
- Remove as a secondary destructive action when an installation is present;
- actionable, privacy-bounded failure copy;
- disabled actions while an operation is in progress;
- a secondary Restart helper action that remains visible but disabled during plugin lifecycle work;
- accessibility labels, keyboard reachability, status announcements, and no color-only state communication.
No new top-level route, wizard, diff viewer, background poller, onboarding framework, or generalized plugin manager is part of this feature.
No product implementation begins until a scoped proof exercises the actual
bundled Codex CLI in the owner's normal macOS login. This feature exists to
manage that owner's Codex plugin, so the CLI may read and change the owner's
Codex-managed state only for the exact release-radar marketplace/plugin.
Every mutation uses supported fixed CLI operations. For the explicit modified
installation requirement, the helper may read only the three expected files
under the exact versioned release-radar installed cache root to compute its
digest; it never writes cache/configuration directly or reads unrelated Codex
state. It fails closed if the stable name, root, version, file inventory, or
digest is unrecognized.
The completed no-skill RED pressure scenario remains historical skill-authoring evidence. It is not rerun and no additional stochastic agent evaluation is a feasibility gate. The remaining proof is deterministic and product-shaped.
The signed bridge/temporary SQLite transition, wrong-tool no-delta case, direct packaged AgentTools handshake, and exact owned CLI PID/PGID cancellation have already passed and remain accepted cumulative Task 1 evidence. This correction does not rerun them. Its only remaining live sequence is allowed MCP preflight, exact bundled-key absence, derived plugin install while leaving the legacy entry untouched, read-only bundled-server verification, one normal fresh-task skill/MCP/tool- exposure proof, plugin/marketplace cleanup, and exact unchanged initial MCP- state verification.
The focused correction is test-first. RED changes only self-test expectations
to require exact release_radar keys in both canonical and derived manifests,
fixed mcp get release_radar, runtime expectation release_radar, and newly
computed canonical digests while the hyphenated fixtures/implementation remain;
the inert suite must fail those affected groups. GREEN changes only the two
fixture .mcp.json files and the minimum focused controller/parser/digest
expectations, then compiles with warnings as errors and passes the full inert
suite. Earlier exact fixture digests remain historical evidence for the old
bytes and must not be reported as the corrected canonical digests.
The gate must prove:
- In the owner's current login, supported read-only CLI preflight records only
the scoped
release-radarplugin/marketplace state, the legacyrelease-radarMCP entry, exact bundledrelease_radarabsence, and an opaque before-state for unrelated entries. Task 1 requires the target plugin and marketplace to be absent. The legacy MCP entry may be absent or may match the exact recognized legacy contract above; any other same-name state stops before mutation. The proof restores and verifies the complete allowed initial state after success or failure, and the opaque unrelated marketplace/plugin fingerprint remains unchanged. Task 1 makes no assertion about unobservable settings, approval state, or plugin-choice state and introduces no separate mechanism for them. No such content is returned, logged, or persisted by the helper. - Before lifecycle mutation, concrete RED/GREEN probe self-tests exercise strict JSON, timeout, output overflow, path confinement, digest, and executable identity inputs.
- A fixed local marketplace can be registered or discovered without editing Codex configuration directly, and identity/path conflicts fail closed.
- Clean v1 install, status, remove, reinstall, and clean v1-to-v2 update reach the expected postcondition.
- The controller issued no remove during v1-to-v2 update, and supported before/after observations remained installed and enabled under the same plugin identity. This does not claim unobservable internal transient state. The pinned CLI exposes no supported MCP approval or plugin-choice state. Task 1 makes no assertion about those unobservable values and introduces no separate mechanism for them.
- Repeated operations are either safely idempotent or return a normalized non-destructive result.
- Exact-root read-only digesting produces the classifications above for a one-byte skill modification, missing or malformed manifest, syntactically valid changed MCP command, unexpected file, symlink, non-regular entry, rejected path/version, partial target, component replacement, and change-while-read without exposing unrelated Codex state. Derived tests snapshot the complete cache-shaped fixture before status and prove identical bytes and inventory afterward. They also prove that denied configuration and sibling targets are not opened and that structured output and captured logs contain no path.
- After the focused key correction, recompute and record canonical v1/v2
fixture digests. Those corrected fixtures then remain byte-unchanged during
the live proof. Runtime-only copies may replace the fixture MCP command with
the packaged
ReleaseRadarAgentToolspath and must use their own computed digest. Do not reuse the earlier digests for the hyphenated fixture bytes. Earlier accepted real-lifecycle evidence proved installed digest equality for the then-current fixtures and remains behavioral lifecycle evidence. The corrected proof does not rerun v1/update matrices; after installing its runtime-only v2 copy, the existing read-only digester proves that the actual installed digest equals the newly computed derived digest before the fresh task. Supported CLI cleanup then restores target absence. - The completed plan Steps 1–6 lifecycle evidence remains accepted: the official CLI established the absent-to-v1-to-v2-to-absent lifecycle, canonical digest/integrity mechanics for the historical hyphenated fixture bytes, conflict/repeat/partial-state behavior, and unchanged opaque unrelated-state fingerprint. It does not establish the newly computed underscore-fixture digests. The remaining proof does not rerun those matrices.
- Build the current signed app and packaged
ReleaseRadarAgentTools. A derived runtime-only v2 plugin may point.mcp.jsonat that exact packaged tool; canonical fixtures remain byte-unchanged. Leave the allowed initial legacy MCP state untouched. First require exact pinned absence for bundled keyrelease_radar, then install the derived plugin and require supportedmcp get release_radar --jsonto resolve bundled serverrelease_radarto the derived tool. The accepted direct handshake for that packaged binary supplies protocol2025-06-18, server nameRelease Radar, server version1, and the transition-tool schema. One fresh Codex task must emititem.startedanditem.completedJSONL for onemcp_tool_callwhose server isrelease_radar, tool isrelease_radar_transition_ticket, and arguments are{}. Completion must befailedwith either a nonempty schema/argument-validation error or a Codex approval-policy rejection that prevents server execution, with no result and no Release Radar action. The structured event pair proves the tool is model-callable without requiring an XPC or SQLite mutation. Agent prose is not evidence. Cleanup removes the derived plugin/marketplace and then exactly verifies bundledrelease_radarabsence plus the unchanged legacy entry. Missing or ambiguous runtime or cleanup evidence fails the gate; agent prose alone is not evidence. - Accepted cumulative evidence from the existing signed bridge acceptance
boundary and a fresh temporary app-owned database through
testPackagedSignedToolUsesRegisteredBrokerAndFailsClosedWithoutTheApp. The standalone controller first requires the normal Release Radar app not to be running; it makes no service-status claim. Inside the Release Radar app-hosted acceptance test, publicSMAppServicestatus must be exactly.notRegistered;.notFound, enabled, approval-pending, or unknown state stops the gate. The test registers the exact bridge itself, records ownership only after successful registration, unregisters only when it owns that registration, and verifies final.notRegisteredon every cleanup path. It never useslaunchctl,libproc, or private service state. One realrelease_radar_transition_ticketrequest must travel through the packaged AgentTools, signed bridge, app callback, andDeliveryStoreproduced the expected ticket lane, one request receipt, and exactly one matching sanitized audit. The existing same-team wrong-tool identity was rejected before dispatch with no database delta. This evidence is not rerun for the canonical-name correction. - Accepted cumulative evidence from the focused controller's bounded
cancellation of a directly spawned, fixed read-only Codex CLI request used
POSIX_SPAWN_SETPGROUPand Darwin start-suspended behavior. It verifies direct-child ownership andPID == PGID, signals only that still-owned process/group, continues only as needed for bounded TERM-to-KILL cleanup, reaps the direct child withwaitpid, and verifies PID/group absence within two seconds. No fake CLI, shell, retry matrix, feasibility helper, application harness, Mach service, generated Swift package,launchctlorlibproccorroborator, runtime tracer, injected failure matrix, or process reconciliation framework was introduced. This evidence is not rerun for the canonical-name correction. Production lifecycle-helper registration, peer admission, and exhaustive cleanup tests remain Product Task 2 requirements.
If the real CLI cannot safely support update or marketplace confinement, or if the exact-root read-only digest cannot safely prove integrity, implementation stops at this gate. The result is reported as a blocker; no direct cache/config writer or replacement framework is introduced.
After the feasibility gate passes, one bounded vertical implementation slice covers the package, lifecycle core/helper, persistence, Settings UI, packaging, and existing runtime integration. It requires independent Code Review, QA, Architecture, Security/Privacy, TPM, and Delivery Management approval.
Acceptance evidence includes:
- test-first state and persistence coverage for every lifecycle state and transition;
- isolated real-CLI lifecycle results for v1 and v2 packages;
- modification preservation and explicit reinstall recovery;
- signed XPC admission, executable verification, timeout/output bounds, and proof the helper does not link ReleaseRadarCore or SQLite;
- strict signed-bundle/resource verification;
- Settings visual and accessibility comparison at wide and compact sizes;
- one owner-confirmed Codex task already rooted at the selected repository
exactly matches the canonical authorized root embedded in the copied prompt
and explicitly invokes
$release-radar:release-radarin that same task; for initialization it preserves or creates the applicable repositoryAGENTS.mdguidance and minimum pending-auditdocs/delivery/progress.mdledger, reads those files back, and records the actualAGENTS.mdwith successful audited existing ticketless evidence. This is a one-shot handoff proof, not an MCP read API or synchronization framework; - unchanged behavior for the existing runtime bridge and delivery mutations;
- final status, evidence, decisions, and residual risks in
docs/delivery/progress.mdonly.