From 2d845c39b6a1a43a82ac8ac9f3f62f3d8878bfe2 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Tue, 8 Sep 2026 14:31:40 -0700 Subject: [PATCH 1/4] fix: correct DOT parser and ship-check guidance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Correct the manager-child example so it teaches Graphviz-valid quoted dotted keys, with a guard that renders both the accepted and rejected forms. Harden ship_check so a git-status error remains an exit-zero dirty token and follows the existing dirty escalation edge. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> --- .github/capsule-pipeline/task-runner.dot | 10 +-- .../11-manager-child-dotfile-hitl/README.md | 7 +- tests/test_doc_consistency.py | 90 +++++++++++++++++++ 3 files changed, 99 insertions(+), 8 deletions(-) diff --git a/.github/capsule-pipeline/task-runner.dot b/.github/capsule-pipeline/task-runner.dot index 9e85bbd..b8c6d2d 100644 --- a/.github/capsule-pipeline/task-runner.dot +++ b/.github/capsule-pipeline/task-runner.dot @@ -396,12 +396,12 @@ digraph BacklogTaskRunner { package [shape=box, class="maker", prompt="Both gates passed. Prepare the work for handoff in $target_dir: ensure runner artifacts (.ai/) are excluded from version control via .git/info/exclude (do NOT commit them); create or reuse a branch named task/ (the id is in the task file's frontmatter at $task_file); commit all task changes with a clear conventional commit message stating WHAT changed and WHY (draw on .ai/brief.md and .ai/critique.md; include the standard Amplifier co-authored-by attribution); and write .ai/SHIPPED.md summarizing the goal, what shipped, where the evidence lives (.ai/verify.log, .ai/convergence.jsonl, run events), and anything a reviewer should know."] - // TRACKED-.ai LEAK CHANNEL (T1-7 council): the porcelain grep excludes - // only UNTRACKED .ai entries (^?? .ai); a worker that git-adds/commits - // .ai/ mid-run evades that filter and ships runner artifacts. First - // assertion: `git ls-files .ai/` must be EMPTY, else dirty -> escalate. + // TRACKED-.ai LEAK CHANNEL (T1-7 council): assert first that + // `git ls-files -- .ai/` is EMPTY, then use an explicit pathspec to ask + // porcelain about every path except `.ai/`; either nonempty result means + // dirty -> escalate. ship_check [shape=parallelogram, class="gate", max_retries=0, - tool_command="[ -z \"$(git ls-files .ai/)\" ] && [ -z \"$(git status --porcelain | grep -v -E '^\\?\\? \\.ai')\" ] && git log --oneline -1 | grep -q . && printf shipped || printf dirty"] + tool_command="tracked=$(git ls-files -- .ai/) || { printf dirty; exit 0; }; [ -z \"$tracked\" ] || { printf dirty; exit 0; }; status=$(git status --porcelain -- . ':(exclude).ai/') || { printf dirty; exit 0; }; [ -z \"$status\" ] && git rev-parse --verify -q HEAD^{commit} >/dev/null 2>&1 && printf shipped || printf dirty"] // ---------- budget exhaustion: a decision point, not a fuse ---------- // MUST_WRITE + S2 END-STATE (T1-7 council, 4-1; sam's S3 dissent diff --git a/examples/pipelines/11-manager-child-dotfile-hitl/README.md b/examples/pipelines/11-manager-child-dotfile-hitl/README.md index bac8c7f..3def4c6 100644 --- a/examples/pipelines/11-manager-child-dotfile-hitl/README.md +++ b/examples/pipelines/11-manager-child-dotfile-hitl/README.md @@ -68,6 +68,7 @@ steps: ## DOT parser note Attribute keys containing dots (`manager.max_cycles`, `stack.child_dotfile`) are -written **without** surrounding double-quotes -- the attractor DOT parser stores a -quoted key with its quote characters, which breaks the bare-string lookups the -handlers use. Correct: `manager.max_cycles=1`. Wrong: `"manager.max_cycles"="1"`. +quoted Graphviz keys: `"manager.max_cycles"=1` is correct. The runtime also accepts +bare `manager.max_cycles=1`, but Graphviz rejects it. The parser strips the key +delimiters before handler lookup, so quoting keeps `manager.max_cycles` as the +lookup key. diff --git a/tests/test_doc_consistency.py b/tests/test_doc_consistency.py index 7f65b52..5a40181 100644 --- a/tests/test_doc_consistency.py +++ b/tests/test_doc_consistency.py @@ -35,6 +35,7 @@ """ import re +import subprocess from pathlib import Path # Root of the bundle repo relative to this test file @@ -195,6 +196,95 @@ def test_house_llm_classification_is_indirect(): ) +# --------------------------------------------------------------------------- +# D-336: manager-child DOT parser guidance must match its adjacent DOT example +# --------------------------------------------------------------------------- + +_MANAGER_CHILD_README_REL = "examples/pipelines/11-manager-child-dotfile-hitl/README.md" +_MANAGER_CHILD_PARENT_REL = "examples/pipelines/11-manager-child-dotfile-hitl/parent.dot" +_MANAGER_CHILD_PARSER_NOTE_HEADING = "## DOT parser note" +_QUOTED_MANAGER_MAX_CYCLES = '"manager.max_cycles"=1' +_BARE_MANAGER_MAX_CYCLES = "manager.max_cycles=1" + + +def _manager_child_parser_note() -> str: + """Return the example's one parser-note section, refusing heading dodges.""" + readme = _read(_MANAGER_CHILD_README_REL) + headings = re.findall( + rf"^{re.escape(_MANAGER_CHILD_PARSER_NOTE_HEADING)}$", + readme, + flags=re.MULTILINE, + ) + assert len(headings) == 1, ( + f"{_MANAGER_CHILD_README_REL}: expected exactly one " + f"'{_MANAGER_CHILD_PARSER_NOTE_HEADING}' heading, found {len(headings)}. " + "Keep the parser teaching in its named final section (D-336)." + ) + return readme.split(_MANAGER_CHILD_PARSER_NOTE_HEADING, 1)[1].strip() + + +def test_manager_child_parser_note_teaches_the_quoted_parent_attribute(): + """The README must teach the Graphviz-valid form its adjacent parent uses (D-336).""" + note = _manager_child_parser_note() + parent = _read(_MANAGER_CHILD_PARENT_REL) + + parent_attr = re.search( + r'^\s*(?P"manager\.max_cycles")=1,$', parent, flags=re.MULTILINE + ) + assert parent_attr is not None, ( + f"{_MANAGER_CHILD_PARENT_REL}: quoted manager.max_cycles fixture attribute " + "not found; the README's parser guidance needs an adjacent executable witness." + ) + assert _QUOTED_MANAGER_MAX_CYCLES in note, ( + f"{_MANAGER_CHILD_README_REL}: parser note must teach the exact quoted " + f"attribute used by parent.dot: `{_QUOTED_MANAGER_MAX_CYCLES}`." + ) + assert _BARE_MANAGER_MAX_CYCLES in note, ( + f"{_MANAGER_CHILD_README_REL}: parser note must retain the bare-form " + f"counterexample `{_BARE_MANAGER_MAX_CYCLES}`." + ) + assert re.search(r"runtime also accepts\s+bare", note), ( + f"{_MANAGER_CHILD_README_REL}: parser note must distinguish runtime " + "acceptance from Graphviz syntax validity." + ) + assert "Graphviz rejects it" in note, ( + f"{_MANAGER_CHILD_README_REL}: parser note must say the bare dotted key " + "is invalid Graphviz, not merely omit it." + ) + assert re.search(r"strips the key\s+delimiters", note), ( + f"{_MANAGER_CHILD_README_REL}: parser note must explain that quoted key " + "delimiters do not enter handler lookup keys." + ) + + +def test_manager_child_parser_note_rendering_witnesses_match_its_teaching(): + """Real Graphviz must render the documented positive form and reject the negative.""" + def render(source: str) -> subprocess.CompletedProcess[str]: + return subprocess.run( + ["dot", "-Tsvg"], + input=source, + text=True, + capture_output=True, + check=False, + ) + + quoted = render(f"digraph {{ manager [{_QUOTED_MANAGER_MAX_CYCLES}] }}") + assert quoted.returncode == 0, ( + "Graphviz rejected the quoted dotted-key form the parser note teaches:\n" + f"{quoted.stderr}" + ) + assert " Date: Tue, 8 Sep 2026 14:54:23 -0700 Subject: [PATCH 2/4] style(tests): format manager-child documentation guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Format only the D-336 test code introduced by the preceding source maintenance commit; no production bytes change. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> --- tests/test_doc_consistency.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_doc_consistency.py b/tests/test_doc_consistency.py index 5a40181..408fc8b 100644 --- a/tests/test_doc_consistency.py +++ b/tests/test_doc_consistency.py @@ -201,7 +201,9 @@ def test_house_llm_classification_is_indirect(): # --------------------------------------------------------------------------- _MANAGER_CHILD_README_REL = "examples/pipelines/11-manager-child-dotfile-hitl/README.md" -_MANAGER_CHILD_PARENT_REL = "examples/pipelines/11-manager-child-dotfile-hitl/parent.dot" +_MANAGER_CHILD_PARENT_REL = ( + "examples/pipelines/11-manager-child-dotfile-hitl/parent.dot" +) _MANAGER_CHILD_PARSER_NOTE_HEADING = "## DOT parser note" _QUOTED_MANAGER_MAX_CYCLES = '"manager.max_cycles"=1' _BARE_MANAGER_MAX_CYCLES = "manager.max_cycles=1" @@ -259,6 +261,7 @@ def test_manager_child_parser_note_teaches_the_quoted_parent_attribute(): def test_manager_child_parser_note_rendering_witnesses_match_its_teaching(): """Real Graphviz must render the documented positive form and reject the negative.""" + def render(source: str) -> subprocess.CompletedProcess[str]: return subprocess.run( ["dot", "-Tsvg"], @@ -280,8 +283,7 @@ def render(source: str) -> subprocess.CompletedProcess[str]: "Graphviz accepted the bare dotted-key form that the parser note calls invalid." ) assert "syntax error" in bare.stderr.lower(), ( - "The negative Graphviz witness failed for an unexpected reason:\n" - f"{bare.stderr}" + f"The negative Graphviz witness failed for an unexpected reason:\n{bare.stderr}" ) From c4e06cdaa62ac970b4efaec043005e6ea0a776d1 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Tue, 8 Sep 2026 15:16:37 -0700 Subject: [PATCH 3/4] fix(ci): install Graphviz for root documentation guards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Provision the root guard job with the Graphviz executable required by its D-336 rendering witness. Keep the documented job prerequisites accurate. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> --- .github/workflows/ci.yml | 6 ++++++ AGENTS.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 149b536..641b3cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -185,6 +185,12 @@ jobs: - name: Install uv uses: astral-sh/setup-uv@v4 + - name: Install graphviz + run: | + sudo apt-get update + sudo apt-get install -y graphviz + dot -V + - name: Install pytest run: uv pip install --system pytest pyyaml diff --git a/AGENTS.md b/AGENTS.md index 409d81d..c1d559d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -31,7 +31,7 @@ That document also carries the per-change-class evidence table, the four-layer d ## Key directories - `modules/tool-report-outcome/` — the one module this repo owns. -- `tests/` — the root guard harness (the `opinionated-guards` CI job). Asserts on repo-root docs/, examples/, skills/, agents/, context/, bundles/, behaviors/ content, and installs nothing but pytest by construction. +- `tests/` — the root guard harness (the `opinionated-guards` CI job). Asserts on repo-root docs/, examples/, skills/, agents/, context/, bundles/, behaviors/ content, and installs only pytest plus Graphviz for DOT-render proof. - `examples/pipelines/` — canonical pipeline patterns. Useful as live test fixtures when verifying engine changes. - `specs/` — our spec extensions and the canonical attractor reference. - `docs/CONTRACTS.md` — engine-level contracts: M5 substitution, fail-fast policy, structural concurrency, and cross-consumer guidance. From d07414661ba2909d7001a28e294b3249ade0178a Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Tue, 8 Sep 2026 15:20:38 -0700 Subject: [PATCH 4/4] fix(ci): clarify Graphviz root guard prerequisites The root guard job already installs Graphviz for the documentation render witness. Keep its comments accurate: the guard suite has no engine-module dependency, and Graphviz is an independent DOT-syntax prerequisite. Generated with Amplifier Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com> --- .github/workflows/ci.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 641b3cc..d81c3ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -161,13 +161,14 @@ jobs: # # THE MOVE HAS NOW HAPPENED (the P4 slim, attractor-28x), and Track A # did its job: loop-pipeline is gone from this repo and this job still - # runs the same guard corpus, green, having installed nothing but - # pytest. That is the design working, not a coincidence. + # runs the guard corpus without installing engine modules. Its test + # dependencies are pytest, PyYAML, and Graphviz for DOT render proof. # # Deliberately independent of every module/, by construction: every # moved guard was refactored (where needed) to assert FILE CONTENT -- # doc text, YAML/dot files, script logic loaded by path -- rather than - # importing engine code, so this job installs nothing but pytest itself. + # importing engine code. Graphviz checks the documented DOT syntax + # independently; it does not introduce an engine-module dependency. # A handful of guards that genuinely could not be decoupled from the # live engine parser/linter (e.g. the examples/ lint-clean sweep, the # shipped-graph structural contract checks) deliberately stayed behind