← Về thư mục
- make_tree · function · L33-L65 — def make_tree(tmp_path: Path) -> Path
- first_pair · function · L68-L71 — def first_pair() -> tuple[str, str]
- rewrite_tools_line · function · L74-L77 — def rewrite_tools_line(path: Path, new_line: str) -> None
- errs_for · function · L80-L81 — def errs_for(tmp_path: Path, rel: str) -> list[str]
- test_real_repo_passes · function · L86-L87 — def test_real_repo_passes()
- test_green_tree_passes · function · L92-L94 — def test_green_tree_passes(tmp_path)
- test_symmetric_bash_readd_fails_on_both_files · function · L99-L108 — def test_symmetric_bash_readd_fails_on_both_files(tmp_path): # THE #524 drift scenario: source+mirror edited together to re-add Bash. # Mirror-sync stays green (byte-equal pair); this lint must fire on BOTH, # via the semantic value check AND the byte-exact witness.
- test_dropped_tool_fails · function · L111-L115 — def test_dropped_tool_fails(tmp_path)
- test_typoed_tool_name_fails · function · L118-L122 — def test_typoed_tool_name_fails(tmp_path)
- test_trailing_whitespace_is_byte_drift · function · L125-L131 — def test_trailing_whitespace_is_byte_drift(tmp_path): # A trailing space keeps the semantic value intact but breaks the # byte-exact witness — the exact-form pin must still fire.
- test_crlf_conversion_is_byte_drift · function · L134-L143 — def test_crlf_conversion_is_byte_drift(tmp_path): # A symmetric LF->CRLF conversion leaves YAML semantics intact, so only # the CR-sensitive byte witness catches it (codex round-1 P2).
- test_missing_tools_key_fails_as_widening · function · L146-L153 — def test_missing_tools_key_fails_as_widening(tmp_path): # Dropping the key silently widens capability (agent inherits ALL tools).
- test_escaped_duplicate_key_fails · function · L156-L168 — def test_escaped_duplicate_key_fails(tmp_path): # The sharpest round-2 bypass: keep the pinned bare line AND add a #
s-escaped "tools": duplicate. safe_load collapses the two # to the last-wins canonical value; the duplicate-preserving node tree # sees TWO tools keys and fires.
- test_quoted_duplicate_key_fails · function · L171-L176 — def test_quoted_duplicate_key_fails(tmp_path)
- test_missing_allowlisted_file_fails · function · L179-L183 — def test_missing_allowlisted_file_fails(tmp_path)
- test_no_frontmatter_fails · function · L186-L190 — def test_no_frontmatter_fails(tmp_path)
- test_uncomposable_frontmatter_fails_closed · function · L193-L199 — def test_uncomposable_frontmatter_fails_closed(tmp_path)
- test_body_tools_line_does_not_satisfy_pin · function · L202-L210 — def test_body_tools_line_does_not_satisfy_pin(tmp_path): # The pinned line must live in FRONTMATTER; a body mention is not a grant.
- bash_fixture · function · L215-L221 — def bash_fixture(tmp_path, frontmatter_body: str) -> list[str]
- test_bucket_a_agent_advertising_bash_fails · function · L224-L227 — def test_bucket_a_agent_advertising_bash_fails(tmp_path)
- test_quoted_string_value_bash_fails · function · L230-L233 — def test_quoted_string_value_bash_fails(tmp_path)
- test_quoted_name_with_bash_fails · function · L236-L239 — def test_quoted_name_with_bash_fails(tmp_path)
- test_flow_list_bash_fails · function · L242-L245 — def test_flow_list_bash_fails(tmp_path)
- test_block_list_bash_fails · function · L248-L252 — def test_block_list_bash_fails(tmp_path)
- test_inline_comment_bash_fails · function · L255-L259 — def test_inline_comment_bash_fails(tmp_path)
- test_permission_specifier_bash_fails · function · L262-L265 — def test_permission_specifier_bash_fails(tmp_path)
- test_bashoutput_is_a_different_tool_and_passes · function · L268-L273 — def test_bashoutput_is_a_different_tool_and_passes(tmp_path): # Exact base-name match: BashOutput grants no shell; a prefix match # would false-fire on it.
- test_lowercase_bash_is_a_different_tool_and_passes · function · L276-L281 — def test_lowercase_bash_is_a_different_tool_and_passes(tmp_path): # Tool names are exact:
bash (lowercase) is not the shell-granting # Bash tool. Case-folding would false-fire on a legitimate value.
- test_bom_padded_bash_fails · function · L284-L290 — def test_bom_padded_bash_fails(tmp_path): # #524 r9: a BOM (U+FEFF) is NOT stripped by str.strip(), so
Bash # would survive as a token != Bash and slip the membership test. # _fold drops Cf format chars so it collapses back to Bash.
- test_zero_width_space_padded_bash_fails · function · L293-L297 — def test_zero_width_space_padded_bash_fails(tmp_path): # #524 r9: zero-width space (U+200B) — a Cf format char str.strip() leaves.
- test_zero_width_non_joiner_padded_bash_fails · function · L300-L304 — def test_zero_width_non_joiner_padded_bash_fails(tmp_path): # #524 r9: zero-width non-joiner (U+200C) — another Cf format char.
- test_fullwidth_bash_fails · function · L307-L312 — def test_fullwidth_bash_fails(tmp_path): # #524 r9: fullwidth "Bash" (U+FF22 etc.) is a compatibility homoglyph; # NFKC in _fold folds it onto ASCII "Bash".
- test_fullwidth_paren_permission_specifier_bash_fails · function · L315-L321 — def test_fullwidth_paren_permission_specifier_bash_fails(tmp_path): # #524 r10: fullwidth parens U+FF08/U+FF09 in a permission specifier. The # ASCII "(" split misses them, so folding must happen BEFORE the split — # otherwise NFKC leaves the whole "Bash(git:*)" as one token != "Bash".
- test_fullwidth_bash_and_paren_specifier_fails · function · L324-L329 — def test_fullwidth_bash_and_paren_specifier_fails(tmp_path): # #524 r10: both the tool name AND its parens fullwidth — the whole thing # must fold to ASCII "Bash" and be caught.
- test_fullwidth_comma_separated_bash_fails · function · L332-L339 — def test_fullwidth_comma_separated_bash_fails(tmp_path): # #524 r11: fullwidth comma U+FF0C. split(",") misses it, so "Read,Bash" # stays one token — folding must happen on the WHOLE value BEFORE the comma # split (the r11 corollary of r10), or "Read,Bash" folds to "Read,Bash" # (one token != "Bash") and slips.
- test_small_comma_separated_bash_fails · function · L342-L347 — def test_small_comma_separated_bash_fails(tmp_path): # #524 r11: small comma U+FE50 — another compatibility comma NFKC folds to # ASCII "," only if the fold precedes the split.
- test_fullwidth_comma_and_name_and_paren_bash_fails · function · L350-L355 — def test_fullwidth_comma_and_name_and_paren_bash_fails(tmp_path): # #524 r11: fullwidth comma + fullwidth name + fullwidth parens all at once # — the whole-value fold must reduce it to Read + Bash.
- test_nfkc_stable_alt_separator_is_not_a_bash_grant · function · L358-L372 — def test_nfkc_stable_alt_separator_is_not_a_bash_grant(tmp_path): # #524 r12 (convergence boundary, documented as a NON-bug): an alternate # separator that NFKC does NOT fold to ASCII "," — e.g. an ideographic comma # U+3001, an Arabic comma U+060C, or a semicolon — keeps "Read、Bash" as ONE # token for EVERYONE: the string is a single YAML scalar, this lint splits # only on ASCII "," (and any NFKC-normalizing consumer would too), so no # consumer extracts a bare "Bash" from it and no shell is granted. The # fullwidth comma/paren cases fail (above) precisely because NFKC DOES fold # them into the ASCII separators the split honors; these do not. Not # flagging this is correct — flagging it would be a false positive a future # maintainer might "fix" by over-broadening the separator set.
- test_bom_canonical_allowlist_value_still_fires_byte_witness · function · L375-L387 — def test_bom_canonical_allowlist_value_still_fires_byte_witness(tmp_path): # #524 r9 companion: folding the SEMANTIC check must not weaken invariant 1. # A plugin agent whose tools value is BOM-padded canonical now folds to the # five canonical tools semantically — but the additive byte-witness must # STILL fire (the raw line is not byte-equal to PINNED_TOOLS_LINE).
- test_repeated_identical_scalars_are_not_aliases · function · L390-L398 — def test_repeated_identical_scalars_are_not_aliases(tmp_path): # False-positive guard for the alias-by-shared-identity detector: #
yaml.compose does NOT intern identical scalar values (each gets a # distinct node), so a clean file repeating a value must still pass.
- test_non_string_manifest_agent_key_still_reconciles · function · L401-L410 — def test_non_string_manifest_agent_key_still_reconciles(tmp_path): # A manifest whose
agents mapping carries a non-string key alongside # the real ones must not break reconciliation of the real Bucket A agent.
- test_nested_list_member_fails_closed · function · L413-L418 — def test_nested_list_member_fails_closed(tmp_path): # A non-scalar list member is an unrecognized shape — must not be # silently stringified into a passing value (codex round-2 P2).
- test_mapping_list_member_fails_closed · function · L421-L424 — def test_mapping_list_member_fails_closed(tmp_path)
- test_mapping_tools_value_fails_closed · function · L427-L430 — def test_mapping_tools_value_fails_closed(tmp_path)
- test_typed_scalar_tools_on_bucket_a_fails_closed · function · L433-L441 — def test_typed_scalar_tools_on_bucket_a_fails_closed(tmp_path): # A Bucket A
tools that is an int/bool/null/timestamp scalar is an # unrecognized shape — the reconciliation cannot confirm it excludes # Bash, so it fails closed (scoped to Bucket A; out-of-scope agents with # a nonsense tools value are not this lint's concern).
- test_bare_bash_scalar_fails_closed · function · L444-L449 — def test_bare_bash_scalar_fails_closed(tmp_path): # Bash as the whole scalar value (not a list member) still resolves to # the
Bash base name.
- test_merge_nested_in_sequence_fails_closed · function · L452-L459 — def test_merge_nested_in_sequence_fails_closed(tmp_path): # The merge/alias walk must recurse into sequence values, not only # mapping values — a
<</alias buried in a list still fails closed.
- test_merge_tagged_complex_key_fails_closed · function · L462-L472 — def test_merge_tagged_complex_key_fails_closed(tmp_path): # codex round-5 P1: the merge tag can land on a non-scalar KEY # (
? !!merge [x] is a merge-tagged sequence key). safe_load applies # merge semantics and injects tools: [Read, Bash]; the tag check must # fire on any node type, not just scalars.
- test_deeply_nested_frontmatter_does_not_crash · function · L475-L487 — def test_deeply_nested_frontmatter_does_not_crash(tmp_path): # codex round-5 P2: a pathologically deep flow sequence must never crash # the lint with an unhandled traceback — check() must RETURN. Whether a # given depth trips RecursionError is Python-version-dependent (3.14 # tolerates far deeper nesting than 3.11), so the version-independent # contract is "returns, does not raise", not "fails at depth N". A # depth that does NOT recurse past the limit is a harmless (if odd) file # and legitimately passes; the fail-closed path is exercised # deterministically by test_recursion_error_fails_closed below.
- test_compose_recursion_error_maps_to_none · function · L490-L506 — def test_compose_recursion_error_maps_to_none(): # _mapping_node must turn a RecursionError from yaml.compose into None # (→ the caller emits a fail-closed "does not compose" error), not let it # escape. Driven by monkeypatching compose to raise, so it is # deterministic and Python-version-independent (unlike relying on a # specific nesting depth tripping the interpreter's own limit).
- boom · function · L498-L499 — def boom(a, *k)
- test_walk_recursion_error_fails_closed · function · L509-L525 — def test_walk_recursion_error_fails_closed(): # _uses_merge_or_alias must treat a RecursionError in the walk as True # (fail closed — the tree could not be proven clean). Forced # deterministically: a MappingNode whose
.value is an iterable that # raises RecursionError on iteration (standing in for a walk that # recurses past the interpreter limit), so the test does not depend on # any Python-version recursion depth.
- ExplodingValue · class · L519-L521 — class ExplodingValue
- iter · method · L520-L521 — def iter(self)
- test_uncomposable_bucket_a_frontmatter_fails_closed · function · L528-L534 — def test_uncomposable_bucket_a_frontmatter_fails_closed(tmp_path): # Frontmatter that won't compose can't be cleared by name — fail closed # (codex round-2 P2: a quoted/indented name under malformed YAML must # not be silently skipped).
- test_merge_key_injecting_bash_fails_closed · function · L537-L546 — def test_merge_key_injecting_bash_fails_closed(tmp_path): # codex round-3:
<<: *base merges a tools: [..., Bash] the # duplicate-preserving node scan never sees (the top level only shows a # literal << key). The composed node tree carries the merge tag — # detected there, not by text, and failed closed.
- test_flow_merge_with_quoted_hash_key_fails_closed · function · L549-L558 — def test_flow_merge_with_quoted_hash_key_fails_closed(tmp_path): # codex round-3 P1-2: a
# inside a quoted flow key fooled the old # text-scan comment strip; the node-tree merge-tag detector is immune.
- test_alias_value_bash_fails_closed · function · L561-L567 — def test_alias_value_bash_fails_closed(tmp_path): # An alias makes
tools share another node's value — the node tree sees # the SAME node object twice (shared identity). Fail closed.
- test_ampersand_in_quoted_value_is_not_an_anchor · function · L570-L579 — def test_ampersand_in_quoted_value_is_not_an_anchor(tmp_path): # False-positive guard: a literal
&/* inside a quoted scalar is not a # YAML anchor/alias (the node carries a plain str value) and must NOT # fail the clean file — the node-tree detector, unlike a text scan, sees # this correctly.
- test_allowlisted_file_with_alias_fails_closed · function · L582-L591 — def test_allowlisted_file_with_alias_fails_closed(tmp_path): # Invariant 1 also fails closed on aliases (a
<</alias could inject the # pinned value from elsewhere, defeating the value lock).
- test_indented_fence_in_block_scalar_does_not_truncate · function · L594-L605 — def test_indented_fence_in_block_scalar_does_not_truncate(tmp_path): # codex round-3 P1-1: an indented
--- inside a description: | block # scalar must NOT be read as the closing fence — doing so truncated the # block and hid a Bucket A name + tools: Read, Bash below it. Only a # column-0 --- closes frontmatter.
- test_bom_prefixed_bucket_a_bash_fails_closed · function · L608-L619 — def test_bom_prefixed_bucket_a_bash_fails_closed(tmp_path): # A leading UTF-8 BOM makes
--- fail the column-0 fence match, so # the file would read as frontmatter-less and skip invariant 2 — while a # real YAML reader strips the BOM and sees tools: Read, Bash. _read_raw # strips the BOM so the two agree; the smuggled Bash fails closed.
- test_bom_prefixed_clean_file_passes · function · L622-L628 — def test_bom_prefixed_clean_file_passes(tmp_path)
- test_block_scalar_containing_tools_text_no_false_positive · function · L631-L643 — def test_block_scalar_containing_tools_text_no_false_positive(tmp_path): # codex round-6 P2: a
description: | block scalar whose body contains a # tools: ... line must NOT trip the byte-witness — that line is not the # tools KEY. The witness is anchored to the composed key's own line, so # a clean allowlisted file with such documentation passes.
- test_block_scalar_containing_triple_dash_is_not_a_fence · function · L646-L655 — def test_block_scalar_containing_triple_dash_is_not_a_fence(tmp_path): # The companion false-positive: a block scalar that legitimately contains # an indented
--- line must still parse the real keys below it and PASS # a clean file.
- test_escaped_tools_key_fires_byte_witness_on_allowlisted · function · L658-L668 — def test_escaped_tools_key_fires_byte_witness_on_allowlisted(tmp_path): # codex round-3 P2-1: replacing the pinned line with an escaped-key # spelling makes raw_lines empty; the byte witness must still fire # (require the verbatim pinned line), and the semantic check fires too.
- test_duplicate_tools_on_bucket_a_fails_closed · function · L671-L678 — def test_duplicate_tools_on_bucket_a_fails_closed(tmp_path): # codex round-4 P2: last-wins would pick
Read, Grep and pass, but a # first-wins parser would grant Bash. Duplicate-key resolution is # parser-dependent, so invariant 2 fails closed (as invariant 1 does).
- test_duplicate_tools_first_wins_bash_fails_closed · function · L681-L685 — def test_duplicate_tools_first_wins_bash_fails_closed(tmp_path)
- test_duplicate_name_one_bucket_a_fails_closed · function · L688-L695 — def test_duplicate_name_one_bucket_a_fails_closed(tmp_path): # A duplicate
name where one resolution is Bucket A: a non-Bucket-A # last-wins name would skip the file, hiding a Bucket A first-wins name + # Bash. Fail closed.
- test_duplicate_name_neither_bucket_a_passes · function · L698-L704 — def test_duplicate_name_neither_bucket_a_passes(tmp_path): # If NO resolution is a Bucket A name, the file is out of scope whichever # way a parser resolves it — no need to fail closed.
- test_nested_bucket_a_agent_declaring_bash_fails_closed · function · L707-L718 — def test_nested_bucket_a_agent_declaring_bash_fails_closed(tmp_path): # codex round-7 P1: invariant 2 must reach nested agent files (rglob, not # glob) — the runtime guard keys on
name regardless of path, so a # agents/subdir/x.md with a Bucket A name + Bash is a real exposure.
- test_directory_symlink_under_agent_dir_fails_closed · function · L721-L738 — def test_directory_symlink_under_agent_dir_fails_closed(tmp_path): # codex round-8 P1: rglob does not descend into directory symlinks, so a # tracked
agents/nested -> ../payload could hide a Bucket A agent # declaring Bash. Fail closed on the symlink itself.
- test_bare_cr_frontmatter_bucket_a_bash_fails_closed · function · L741-L751 — def test_bare_cr_frontmatter_bucket_a_bash_fails_closed(tmp_path): # codex round-8 P1: bare
\r (old-Mac) is a YAML line break, but a # split-on-\n fence scan reads the file as frontmatter-less and skips # it, hiding a Bucket A tools: Bash. splitlines() recognizes bare CR so # the declaration is caught.
- test_unicode_line_break_before_tools_no_false_positive · function · L754-L770 — def test_unicode_line_break_before_tools_no_false_positive(tmp_path): # codex round-7 P2: YAML counts NEL (U+0085) / LS (U+2028) / PS (U+2029) # as line breaks but str.split("\n") does not. Placed in a quoted value # BEFORE the tools key, they shift YAML's start_mark.line off the # split("\n") index, so a line-based anchor would read the WRONG physical # line. The byte witness anchors via start_mark.index (byte offset), so # the clean allowlisted file is not falsely rejected and no non-verbatim # key line slips past.
- test_non_bucket_a_agent_with_bash_passes · function · L774-L778 — def test_non_bucket_a_agent_with_bash_passes(tmp_path): # Baked into the green fixture (pipeline_orchestrator_agent declares # Bash); assert it raises nothing on its own.
- test_bucket_a_agent_without_tools_key_passes · function · L781-L785 — def test_bucket_a_agent_without_tools_key_passes(tmp_path): # eic_agent in the green fixture has no tools key — inherit is fine; # the runtime guard still fences it.
- test_missing_manifest_fails_closed · function · L788-L792 — def test_missing_manifest_fails_closed(tmp_path)
- test_unparseable_manifest_fails_closed · function · L795-L799 — def test_unparseable_manifest_fails_closed(tmp_path)
- test_valid_json_non_object_manifest_fails_closed · function · L802-L808 — def test_valid_json_non_object_manifest_fails_closed(tmp_path): # A JSON array parses fine but has no
agents mapping — must be a # curated diagnostic, not a traceback (codex round-1 P2).
- test_non_mapping_agents_value_fails_closed · function · L811-L815 — def test_non_mapping_agents_value_fails_closed(tmp_path)
- test_pinned_line_is_the_frozen_514_value · function · L820-L824 — def test_pinned_line_is_the_frozen_514_value(): # Editing the allowlist is a deliberate security-surface change: it must # touch this lint in the same commit. This test is the second witness.
- test_allowlisted_files_are_the_three_pairs · function · L827-L835 — def test_allowlisted_files_are_the_three_pairs()