@@ -433,11 +433,27 @@ export PackOutcome build_and_pack(Options opts, bool modeFromUser,
433433 // Identity is (package, id): an id is unique within the package that
434434 // declared it and nothing more.
435435 std::vector<std::string> distOutputs;
436+ // THE DISTRIBUTABLE IS THE TERMINAL ARTIFACT. A provider may submit a
437+ // chain (`dist-apk`: link, add libraries, align, sign); every output
438+ // is verified below, but the thing a user installs, and the operand
439+ // `mcpp run --format` hands the runner, is an output no other
440+ // introduced action consumes. Measured 2026-09-12: with the first
441+ // output taken as the operand, `adb-run` received the unsigned
442+ // `base.apk` and `adb install` refused it.
443+ std::vector<std::string> distInputs;
436444 for (auto const & a : distCtx->plan .actions ) {
437445 if (a.role != mcpp::manifest::BuildAction::Role::Artifact) continue ;
438446 if (preexistingArtifacts.contains ({a.packageName , a.id })) continue ;
439447 for (auto const & o : a.outputs ) distOutputs.push_back (o);
448+ for (auto const & i : a.inputs ) distInputs.push_back (i);
440449 }
450+ auto absolute_of = [&](std::string const & p) {
451+ auto q = std::filesystem::path (p).is_absolute ()
452+ ? std::filesystem::path (p) : distCtx->plan .outputDir / p;
453+ return q.lexically_normal ();
454+ };
455+ std::set<std::filesystem::path> consumed;
456+ for (auto const & i : distInputs) consumed.insert (absolute_of (i));
441457 // DECLARED AND THEN SUBMITTED NOTHING. The half of the contract a
442458 // member is most likely to get wrong is the gate, and a member whose
443459 // gate never opens leaves a pass that succeeds and produces no
@@ -484,19 +500,23 @@ export PackOutcome build_and_pack(Options opts, bool modeFromUser,
484500 // nothing.
485501 std::error_code ec;
486502 std::vector<std::filesystem::path> reported;
503+ std::vector<std::filesystem::path> intermediate;
487504 for (auto const & o : distOutputs) {
488- auto abs = std::filesystem::path (o).is_absolute ()
489- ? std::filesystem::path (o) : distCtx->plan .outputDir / o;
505+ auto abs = absolute_of (o);
490506 if (!std::filesystem::is_regular_file (abs, ec)
491507 && !std::filesystem::is_directory (abs, ec)) {
492508 mcpp::ui::error (std::format (
493509 " --format {} reported success and produced nothing at {}" ,
494510 opts.formatName , abs.string ()));
495511 return PackOutcome{1 };
496512 }
513+ if (consumed.contains (abs)) { intermediate.push_back (std::move (abs)); continue ; }
497514 mcpp::ui::status (" Packed" , mcpp::ui::shorten_path (abs, pathCtx));
498515 reported.push_back (std::move (abs));
499516 }
517+ // Every output consumed by another: a cycle a provider should not
518+ // write, reported as all outputs rather than as nothing.
519+ if (reported.empty ()) reported = std::move (intermediate);
500520 return PackOutcome{0 , std::move (reported)};
501521 }
502522
0 commit comments