@@ -537,11 +537,7 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
537537 // namespaces (so existing fetcher / lockfile lookups by composite name
538538 // keep working) and the bare `<name>` for the default namespace (so the
539539 // common case stays unchanged).
540- // MUST list every key `fill_inline_spec` below reads. The two are one
541- // decision in two places: this predicate also distinguishes an inline dep
542- // spec from a NESTED namespace table, so a key missing here does not read
543- // as "unknown option" — the table is taken for a namespace and the user is
544- // told their value "must be a string, inline dep table, or nested table".
540+ // MUST list every key `fill_inline_spec` below reads.
545541 // `Manifest.EveryDependencySpecKeyIsAccepted` holds the two in sync.
546542 auto is_dep_spec_key = [](std::string_view k) {
547543 return k == " path" || k == " version" || k == " git"
@@ -551,19 +547,49 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
551547 || k == " backend" || k == " tools"
552548 || k == " host-module" || k == " reexport" ;
553549 };
554- auto looks_like_inline_dep_spec = [&](const t::Table& sub) {
550+ // What makes a table an inline dep spec is that it names a SOURCE. This
551+ // used to be "every key is known", which quietly coupled two unrelated
552+ // things: the discriminator (spec vs nested namespace table) and the
553+ // vocabulary (which keys mean something).
554+ //
555+ // The coupling is a compatibility hazard, not a style problem. A manifest
556+ // using a key introduced after the reader was built did not get "unknown
557+ // option" — the table failed the discriminator, was taken for a NAMESPACE,
558+ // and the user was told their `reexport = true` "must be a string, inline
559+ // dep table, or nested table". Worse, a published package cannot adopt a
560+ // new key at all, because every older client fails to load it outright
561+ // rather than ignoring what it does not understand. That is the same
562+ // property #349 established for the index floor: data must not be able to
563+ // decide whether the program works.
564+ //
565+ // An identity key is an unambiguous discriminator: a nested namespace
566+ // table's keys are PACKAGE names, and no package is named `version` /
567+ // `path` / `git` / `workspace`.
568+ auto looks_like_inline_dep_spec = [](const t::Table& sub) {
555569 if (sub.empty ()) return false ;
556- for (auto & [sk, sv] : sub) {
557- if (! is_dep_spec_key (sk)) return false ;
558- }
559- return true ;
570+ for (auto & [sk, sv] : sub)
571+ if (sk == " path " || sk == " version " || sk == " git " || sk == " workspace " )
572+ return true ;
573+ return false ;
560574 };
561575
562576 auto fill_inline_spec = [&](DependencySpec& spec,
563577 std::string_view section,
564578 std::string_view fqName,
565579 const t::Table& sub) -> std::expected<void , ManifestError>
566580 {
581+ // Now that the discriminator no longer doubles as the vocabulary, an
582+ // unrecognized key can be REPORTED — as a degradation, so `--strict`
583+ // still refuses it, while an ordinary build of a package written for a
584+ // newer mcpp proceeds with the part this one understands. Same
585+ // discipline as the xpkg reader's `xpkgUnknownKeys`: record rather
586+ // than swallow, and never fail the whole load over it.
587+ for (auto & [sk, sv] : sub) {
588+ if (is_dep_spec_key (sk)) continue ;
589+ m.schemaWarnings .push_back (std::format (
590+ " [{}.\" {}\" ] has unrecognized key '{}' (ignored). It may be a "
591+ " typo, or a field a newer mcpp understands." , section, fqName, sk));
592+ }
567593 if (auto it = sub.find (" path" ); it != sub.end () && it->second .is_string ()) spec.path = it->second .as_string ();
568594 if (auto it = sub.find (" version" ); it != sub.end () && it->second .is_string ()) spec.version = it->second .as_string ();
569595 if (auto it = sub.find (" git" ); it != sub.end () && it->second .is_string ()) spec.git = it->second .as_string ();
@@ -664,9 +690,10 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
664690 auto & sub = value.as_table ();
665691 if (!looks_like_inline_dep_spec (sub)) {
666692 return std::unexpected (error (origin, std::format (
667- " [{}.{}] must be a version string or table of "
668- " (path/version/git/rev/tag/branch/features/default-features/"
669- " visibility/backend/workspace/tools/host-module/reexport)" ,
693+ " [{}.{}] must be a version string, or a table naming a "
694+ " source (one of path/version/git/workspace) alongside any "
695+ " of rev/tag/branch/features/default-features/visibility/"
696+ " backend/tools/host-module/reexport" ,
670697 section, key)));
671698 }
672699 if (auto r = fill_inline_spec (spec, section, key, sub); !r) return r;
0 commit comments