@@ -94,6 +94,12 @@ func TestCodexPluginDirAdvisoryPresenceAndAbsence(t *testing.T) {
9494 if ! strings .Contains (stderr .String (), advisory ) {
9595 t .Fatalf ("stderr missing the version-masquerade advisory: %q" , stderr .String ())
9696 }
97+ if ! strings .Contains (stderr .String (), "as " + channelPluginID (devBranch )) {
98+ t .Fatalf ("advisory must name the selected codex plugin id: %q" , stderr .String ())
99+ }
100+ if ! strings .Contains (stderr .String (), "Removed other Spacedock Codex channels" ) {
101+ t .Fatalf ("advisory must name the sibling-provider cleanup: %q" , stderr .String ())
102+ }
97103 if ! strings .Contains (stderr .String (), "not necessarily its current HEAD" ) {
98104 t .Fatalf ("advisory lost its meaning-bearing clause: %q" , stderr .String ())
99105 }
@@ -139,6 +145,73 @@ func TestInstallCodexPluginDirInstallsViaSharedHelper(t *testing.T) {
139145 if ! strings .Contains (stderr .String (), "version-masquerade advisory" ) {
140146 t .Fatalf ("install --host codex --plugin-dir missing the advisory: %q" , stderr .String ())
141147 }
148+ if ! strings .Contains (stderr .String (), "as " + channelPluginID (devBranch )) {
149+ t .Fatalf ("install --host codex --plugin-dir must name the selected plugin id: %q" , stderr .String ())
150+ }
151+ if ! strings .Contains (stderr .String (), "Removed other Spacedock Codex channels" ) {
152+ t .Fatalf ("install --host codex --plugin-dir must name the sibling-provider cleanup: %q" , stderr .String ())
153+ }
154+ }
155+
156+ // TestInstallCodexLocalPluginDirLeavesOnePromptInputProvider is AC-1's hermetic
157+ // host smoke. It seeds a fresh CODEX_HOME with BOTH Spacedock channels using raw
158+ // codex commands, then runs the production --plugin-dir install helper for the
159+ // selected edge checkout. `codex debug prompt-input` is Codex's own model-visible
160+ // skill list renderer, so the assertion observes provider resolution without an
161+ // LLM call: exactly one `spacedock:first-officer` entry remains, and it is the
162+ // selected checkout's provider.
163+ func TestInstallCodexLocalPluginDirLeavesOnePromptInputProvider (t * testing.T ) {
164+ codexBin , err := exec .LookPath ("codex" )
165+ if err != nil {
166+ t .Skip ("codex not on PATH; prompt-input provider smoke requires the host CLI" )
167+ }
168+ saved := devBranch
169+ devBranch = "next"
170+ defer func () { devBranch = saved }()
171+
172+ tmp := t .TempDir ()
173+ codexHomeDir := filepath .Join (tmp , "codexhome" )
174+ mustMkdir (t , codexHomeDir )
175+ t .Setenv ("CODEX_HOME" , codexHomeDir )
176+
177+ stableCheckout := buildCodexFirstOfficerCheckout (t , filepath .Join (tmp , "stable-checkout" ), "1.0.0" , "STABLE_PROVIDER_SPIKE" )
178+ oldEdgeCheckout := buildCodexFirstOfficerCheckout (t , filepath .Join (tmp , "old-edge-checkout" ), "2.0.0" , "OLD_EDGE_PROVIDER_SPIKE" )
179+ selectedCheckout := buildCodexFirstOfficerCheckout (t , filepath .Join (tmp , "selected-checkout" ), "2.0.1" , "SELECTED_PROVIDER_SPIKE" )
180+
181+ stableInstall , err := WriteCodexLocalMarketplace (filepath .Join (tmp , "stable-marketplace" ), stableCheckout , "spacedock" )
182+ if err != nil {
183+ t .Fatalf ("build stable local marketplace: %v" , err )
184+ }
185+ oldEdgeInstall , err := WriteCodexLocalMarketplace (filepath .Join (tmp , "old-edge-marketplace" ), oldEdgeCheckout , "spacedock-edge" )
186+ if err != nil {
187+ t .Fatalf ("build old edge local marketplace: %v" , err )
188+ }
189+
190+ runHost (t , codexBin , os .Environ (), "plugin" , "marketplace" , "add" , stableInstall .MarketplaceRoot )
191+ runHost (t , codexBin , os .Environ (), "plugin" , "add" , "spacedock@spacedock" )
192+ runHost (t , codexBin , os .Environ (), "plugin" , "marketplace" , "add" , oldEdgeInstall .MarketplaceRoot )
193+ runHost (t , codexBin , os .Environ (), "plugin" , "add" , "spacedock@spacedock-edge" )
194+
195+ if err := installCodexLocalPluginDir (execHost {}, selectedCheckout , io .Discard ); err != nil {
196+ t .Fatalf ("install selected --plugin-dir checkout: %v" , err )
197+ }
198+
199+ cmd := exec .Command (codexBin , "debug" , "prompt-input" , "probe" )
200+ cmd .Env = os .Environ ()
201+ out , err := cmd .CombinedOutput ()
202+ if err != nil {
203+ t .Fatalf ("codex debug prompt-input failed: %v\n %s" , err , out )
204+ }
205+ rendered := string (out )
206+ if got := strings .Count (rendered , "spacedock:first-officer:" ); got != 1 {
207+ t .Fatalf ("prompt input has %d spacedock:first-officer providers, want exactly 1\n %s" , got , rendered )
208+ }
209+ if strings .Contains (rendered , "STABLE_PROVIDER_SPIKE" ) || strings .Contains (rendered , "OLD_EDGE_PROVIDER_SPIKE" ) {
210+ t .Fatalf ("prompt input still exposes stale Spacedock providers:\n %s" , rendered )
211+ }
212+ if ! strings .Contains (rendered , "SELECTED_PROVIDER_SPIKE" ) {
213+ t .Fatalf ("prompt input missing the selected checkout provider:\n %s" , rendered )
214+ }
142215}
143216
144217// TestInstallCodexLocalPluginDirResolvesOnEdgeChannel is AC-2: a --plugin-dir codex
@@ -192,3 +265,14 @@ func buildCodexPluginCheckout(t *testing.T, root, version string) string {
192265 mustWrite (t , filepath .Join (root , "skills" , "demo" , "SKILL.md" ), "---\n name: demo\n description: demo skill\n ---\n demo\n " )
193266 return root
194267}
268+
269+ func buildCodexFirstOfficerCheckout (t * testing.T , root , version , description string ) string {
270+ t .Helper ()
271+ mustMkdir (t , filepath .Join (root , ".codex-plugin" ))
272+ mustMkdir (t , filepath .Join (root , "skills" , "first-officer" ))
273+ mustWrite (t , filepath .Join (root , ".codex-plugin" , "plugin.json" ),
274+ `{ "name": "spacedock", "version": "` + version + `", "requires-contract": ">=3,<4", "skills": "./skills/" }
275+ ` )
276+ mustWrite (t , filepath .Join (root , "skills" , "first-officer" , "SKILL.md" ), "---\n name: first-officer\n description: " + description + "\n ---\n " + description + "\n " )
277+ return root
278+ }
0 commit comments