fix: scope Door A's ownership scan to its own filename prefix
The §3 ownership scan globbed every *.md file regardless of which door wrote it, then unconditionally unlinked whatever _is_ingest_owned agreed to. Because _is_ingest_owned reads through the line-oriented parser that flattens nested blocks (pinned in test_two_nested_block_mappings_sharing_a_key_collide_in_the_scalar_parser), a Door B/C file whose nested content happened to share a key name with the ownership markers (generated, ingest_manifest) could get promoted to top level and spoof ownership -- silently deleting content this door never wrote. Scoping the glob to ingest_prefix closes this by construction: a Door B/C file is never even a candidate for the scan, regardless of what its frontmatter parses to. Traced from a coordination tip from portfolio-optimiser-claude about the same flattening mechanism hitting their `type` field.
This commit is contained in:
parent
685ccb0083
commit
7c608bed1a
3 changed files with 48 additions and 6 deletions
|
|
@ -457,10 +457,20 @@ def materialize_bundle(
|
|||
staged_names = {name for name, _ in staged}
|
||||
|
||||
# §3 ownership scan (sorted for determinism): only files carrying the
|
||||
# ingest stamp are ours to replace.
|
||||
# ingest stamp are ours to replace. Globs by THIS door's prefix, not just
|
||||
# the shared suffix: `_is_ingest_owned` reads through the line-oriented
|
||||
# parser that flattens nested blocks (pinned in
|
||||
# test_two_nested_block_mappings_sharing_a_key_collide_in_the_scalar_parser),
|
||||
# so a Door B/C file whose nested content happens to share a key name
|
||||
# with the ownership markers could otherwise spoof ownership here and get
|
||||
# unlinked below — content this door never wrote. Scoping the glob to
|
||||
# `ingest_prefix` closes that by construction: a Door B/C file is never
|
||||
# even a candidate, regardless of what its frontmatter parses to.
|
||||
owned = {
|
||||
path.name
|
||||
for path in sorted(bundle.glob(f"*{profile.paths.concept_suffix}"))
|
||||
for path in sorted(
|
||||
bundle.glob(f"{profile.paths.ingest_prefix}*{profile.paths.concept_suffix}")
|
||||
)
|
||||
if path.name != profile.index.name
|
||||
and _is_ingest_owned(path, manifest_file.stem, profile=profile)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -340,6 +340,37 @@ def test_replacement_leaves_another_manifests_stamped_file(file_setup: tuple[Pat
|
|||
assert (bundle / "ingest-orders.md").is_file()
|
||||
|
||||
|
||||
def test_a_door_c_import_survives_a_same_stem_materialize_run(
|
||||
file_setup: tuple[Path, Path],
|
||||
) -> None:
|
||||
# The §3 scan used to glob every `*.md` regardless of which door wrote it,
|
||||
# then unconditionally unlink whatever `_is_ingest_owned` agreed to. The
|
||||
# line-oriented parser has no indentation model (pinned in
|
||||
# test_two_nested_block_mappings_sharing_a_key_collide_in_the_scalar_parser),
|
||||
# so a nested block in an externally-imported concept that happens to
|
||||
# share a key name with the ownership markers gets promoted to a
|
||||
# top-level `generated`/`ingest_manifest` pair and can spoof ownership —
|
||||
# deleting content this door never wrote and does not own.
|
||||
manifest_path, bundle = file_setup
|
||||
bundle.mkdir()
|
||||
imported = bundle / "import-external.md"
|
||||
imported.write_text(
|
||||
"---\n"
|
||||
"type: Concept\n"
|
||||
"title: Imported Concept\n"
|
||||
"provenance:\n"
|
||||
" generated: true\n"
|
||||
" ingest_manifest: manifest@deadbeef\n"
|
||||
"---\n\nExternally imported body.\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
materialize_bundle(manifest_path, bundle, INGESTED_AT)
|
||||
|
||||
assert imported.is_file()
|
||||
assert (bundle / "ingest-orders.md").is_file()
|
||||
|
||||
|
||||
def test_second_manifest_does_not_delete_first_manifests_stamped_file(tmp_path: Path) -> None:
|
||||
# §10.2 per-manifest ownership: two manifests writing into ONE bundle each
|
||||
# own only the files whose stamp names them by stem. Running manifest B
|
||||
|
|
|
|||
|
|
@ -164,10 +164,11 @@ def test_profile_paths_and_index_name_reach_disk(file_setup: tuple[Path, Path])
|
|||
def test_second_synthetic_run_replaces_rather_than_colliding(
|
||||
file_setup: tuple[Path, Path],
|
||||
) -> None:
|
||||
"""The §3 ownership scan globs `*{concept_suffix}` and excludes `index.name`.
|
||||
Left on `DEFAULT`, the glob finds nothing under a renamed suffix, `owned` is
|
||||
empty, and the gate refuses to overwrite the file this same code just wrote.
|
||||
The index must also not gain a second link for the same target.
|
||||
"""The §3 ownership scan globs `{ingest_prefix}*{concept_suffix}` and
|
||||
excludes `index.name`. Left on `DEFAULT`, the glob finds nothing under a
|
||||
renamed prefix and suffix, `owned` is empty, and the gate refuses to
|
||||
overwrite the file this same code just wrote. The index must also not
|
||||
gain a second link for the same target.
|
||||
"""
|
||||
manifest_path, bundle = file_setup
|
||||
materialize_bundle(manifest_path, bundle, INGESTED_AT, profile=_SYNTHETIC)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue