Skip to content

Graphify: bounded query normalization and context packets - #982

Draft
jeffhuber wants to merge 30 commits into
mainfrom
claude/914-graph-query-packets
Draft

jeffhuber wants to merge 30 commits into
mainfrom
claude/914-graph-query-packets

Conversation

@jeffhuber

@jeffhuber jeffhuber commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Closes #914. Part of #902.

Adds bounded local Graphify queries and revision-bound context packets to guided sessions. Users can ask symbol, dependency, impact, or related-test questions, then prepare, reuse, and deliver the same approved evidence to Claude, Codex, or Devin. Recipients need no Graphify installation, credentials, or provider access.

The source builder is Claude through Code Mower. Codex supervises validation and provides the independent peer audit. This PR remains draft for serialized roadmap acceptance; it includes no version or release change.

Query and delivery behavior

  • Read the raw nodes/edges document produced by pinned Graphify 0.9.58 with --code-only --no-cluster, preserving extractor direction. The separate NetworkX export requires directed evidence.
  • Resolve exact symbols and paths, including canonical callable labels with only their syntactic argument suffix removed. Preserve directed relationships within cycles and path-selected seed sets while independently bounding node expansion and edge reporting.
  • Apply the normalized policy document allowance end to end, bounded by the adapter cap. Six relationships under the default allowance produce five documents with document_limit, truncation, and partial completeness; protected fetch and guided delivery accept that bounded packet.
  • Report depth truncation only when eligible, unreported relationships remain beyond the boundary. Preserve complete answers when the walk ends at the boundary or every cycle/parallel relationship is already reported.
  • Validate citations against immutable Git blobs and bind packets to both source revision and graph generation. Missing code endpoints produce explicit partial evidence; deliberately excluded non-code records stay outside the query scope. Confidence, truncation, and omission diagnostics survive packet delivery.
  • Bind standalone fetch and guided preparation/replay/delivery/attachment to the consuming revision. Required unavailable context pauses work; optional context degrades. Preserve Fix Coworker citation compatibility and misleading access failures #986 Coworker retrieval-error classification, safe summaries, and cleanup-replay refusal.

Required lifecycle repairs

Real execution exposed gaps missed by schema fixtures. The launcher now supplies the required . scan target in the immutable materialized working directory and collects only the expected graphify-out artifacts. Competing, foreign, pre-existing, and symlink output roots are refused.

On macOS, derive narrowly verified native dependencies from the provider and base interpreter. Validate Mach-O container structure, slice and load-command bounds, and shared-library type before exposing a derived file. Existing ownership and path exclusions, filesystem containment, and network denial remain enforced; unsafe runtime ancestry is refused. Linux behavior is preserved.

Completeness follows the pinned input classifier, including named manifests and extensionless shebang code. Manifest hashes must match bytes supplied before launch and satisfy the pinned post-extraction writer rule. Keep digest and manifest records keyed by exact Git paths; normalization collisions remain partial, while unambiguous single-path macOS spelling differences stay supported. Unsupported code dispatch is counted separately; failures, unknown postconditions, mismatches, and unproven zero-node results remain partial.

Known provider limitations remain explicit: the evaluation's 54-file requeue had stamped manifest rows, and some module-alias cross-file call relationships are absent from extracted evidence.

Validation

Feature head: 1d3258a186adcacd3526bcf9774bc300ade3ed88, including accepted main db4506d2b3232e4c6a7c5683251eeb536b2b8355.

  • Canonical Python 3.12: full suite passed: 3,884 tests run, 16 skipped.
  • 573 focused context tests passed. Four fresh hash-verified, network-denied provider smokes passed: lifecycle/bare-name/path/revision/guided behavior; six-caller policy limits through protected/guided delivery; depth-two chain truncation with exact-boundary/cycle completeness controls; and the same protected/guided fanout checks using a single Unicode filename. Approved recipients received identical packets. No diagnostic provider overrides were used.
  • Ruff, privacy scan, and package workflow guard passed. Python 3.12/3.13/3.14 package CI and Linux/macOS containment CI passed.
  • Independent exact-head Code Mower Codex audit: PASS, with zero P0–P3 findings. code-mower/gate passed.

A frozen generic comparison retained useful navigation beyond the initial seed search in six of eight questions, with every retained citation resolving and no false retained relationships. All eight comparison packets now explicitly report their depth, document, or provider omissions. This supports optional navigation, not semantic completeness or a text-search speedup.

The roadmap orchestrator requires integration of #989 after it merges, followed by fresh validation, exact-head audit, CI, and gate before this PR merges. This feature review does not waive that final integration check.

The Claude builder shell refused interpreter execution; execution evidence comes from the supervisor and CI.

Read the pinned graph schema directly out of a published generation, answer
four bounded deterministic questions, and turn the answer into a
revision-bound context packet the existing delivery path already carries.

The lifecycle (#913) publishes an immutable generation; context_graph (#876)
scores a delivered packet. Between them there was no way to ask the graph a
question and no way to turn an answer into evidence a recipient may read.

- Pinned schema read strictly from the artifact member, with node/edge kinds,
  dangling edges, duplicate ids, inverted spans and out-of-checkout paths all
  refusals rather than best-effort reads.
- Symbol-first, relationship-filtered traversals for impact, dependency,
  symbol and related-test questions, breadth-first over sorted adjacency with
  explicit depth and node budgets; reaching a budget reports truncation.
- Citations validated against the bound commit's tracked census and blobs, not
  the working tree; an unconfirmable location is dropped and reported.
- Packets carry source revision, graph generation, completeness, truncation,
  omission codes and extracted/inferred/ambiguous confidence.
- Required unavailable context blocks dependent work; optional unavailable
  context degrades to ordinary repository tools.
- One approved packet reaches Claude, Codex and Devin through the shared
  delivery path with no provider tools or credentials in the recipient.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jeffhuber

Copy link
Copy Markdown
Contributor Author

Orchestrator validation of current head

Head: 5edb1906f93d5ebb1e15ada78da8d1a2443cef1b.

The new query suite ran with the repository's supported Python 3.12 environment: 40 tests, 1 failure and 2 errors. Focused Ruff passed.

  • test_dependency_walks_along_them and test_traversal_is_deterministic iterate a QueryResult instead of its relations and raise TypeError.
  • test_query_emits_a_metadata_summary_and_writes_a_private_packet expects exit 0 but receives exit 1. Confirm that the fixture authorization is valid at the command's current clock and that packet generation passes the real contract.

The implementation checkpoint on #914 also applies: read the pinned provider's actual nodes/links export, and prove executable guided-context integration rather than only direct packet loading/rendering. The PR remains draft. An independent Code Mower Codex audit is in progress at this exact head; its findings and these test failures should be resolved by the owning Claude builder on the same branch.

@jeffhuber

Copy link
Copy Markdown
Contributor Author

Codex audit (merge-authority lane)

Head SHA: 5edb1906f93d5ebb1e15ada78da8d1a2443cef1b
Findings: P0=0, P1=1, P2=3, P3=0 (blocker policy: any P0/P1/P2 → BLOCKED)

Codex Audit: BLOCKED

Summary:

The patch can expose packet contents through existing output-file permissions, misstate multi-hop relationships, and silently omit seeds. It also introduces a test that necessarily raises TypeError.

Findings:

  • [P1] Enforce private permissions when replacing an existing packet -- src/code_mower/context_graph_command.py:86
    Finding ID: codex:79231b5de19f06d8b3ab
    If --packet-out already exists with mode 0644, os.open ignores the supplied creation mode and writes the evidence into a publicly readable file. The delivery contract then rejects that same file through _check_private_mode. Refuse existing destinations or replace them atomically with a newly created private file.
  • [P2] Render multi-hop relationships using the actual edge endpoints -- src/code_mower/context_graph_query.py:552
    Finding ID: codex:e745b94d061074c8b5f7
    For depth-two results, origin remains the initial seed rather than the preceding node. In the supplied fixture, querying impact on parse_config therefore emits render calls parse_config, although the graph only says render calls load. The accompanying citations also omit the intermediate endpoint. Preserve and cite the actual edge endpoints, or explicitly describe a transitive path instead of asserting a direct relationship.
  • [P2] Report truncation when the seed limit drops matching nodes -- src/code_mower/context_graph_query.py:183
    Finding ID: codex:8b6688fa894a7d1d6516
    When a symbol name or file path matches more than eight nodes, this slice silently excludes the remaining seeds and their reachable relationships. Since run_query only records truncation when the relationship budget is exceeded, the resulting packet can report complete, untruncated evidence despite omitting matching definitions. Carry seed overflow into the result's truncation flag and provider_has_more omission.
  • [P2] Iterate over the query result's relations in the dependency test -- tests/test_context_graph_query.py:294
    Finding ID: codex:7202cc7f74ee6151ef9f
    self.query() returns a QueryResult dataclass, which has no iterator. This comprehension therefore raises TypeError instead of checking dependency traversal, making the newly added test fail. Iterate over .relations, as the other traversal tests do.

@jeffhuber

Copy link
Copy Markdown
Contributor Author

Codex supplemental plan-conformance review

Head: 5edb1906f93d5ebb1e15ada78da8d1a2443cef1b. These are additional blocking acceptance findings from the supervising Codex review, alongside the independent Code Mower audit above.

  • [P1] Read the real pinned Graphify export. load_graph in src/code_mower/context_graph_query.py:258 requires exactly schema, nodes, edges and the invented code_mower.contextGraph.v1 schema. The lifecycle does not produce or normalize that shape: it archives the pinned provider's graph.json, whose official exporter emits nodes/links, Graphify source-location fields, and uppercase confidence labels. Consequently every ordinary graph built by Graphify: private revision-bound local graph lifecycle #913 is rejected by the new query command. Implement a strict bounded adapter for the actual pinned format, with fixture provenance tied to the pinned upstream code, and test build-artifact-to-query compatibility. References: pinned exporter, pinned field validator.
  • [P1] Complete the guided-session delivery route. The new module is only wired to a standalone context-graph query command. Existing session context prepare, deliver, and attachment reuse still load organization connections through the Coworker-specific packet path. Direct tests of load_packet plus render_evidence do not show that an operator can prepare and deliver this graph packet through the promised guided flow. Add that executable integration, preserving optional fallback and rechecking revision/generation on recipient reuse. Demonstrate Claude, Codex, and Devin delivery without provider tools or credentials in the recipient.

Retain the same Claude writer and draft PR. Resolve these findings, the four Code Mower audit findings, and all three observed test failures before requesting a new exact-head review. Keep public validation and PR descriptions free of private local paths.

CI ran the suite this lane's sandbox could not. Three real defects:

- Two tests iterated a QueryResult rather than its relations.
- The command test's authorization envelope expired at the fixed NOW the
  library-level tests use, but the command has no injected clock and
  authorizes against the real one, so every run was refused as expired.
- The new module and doc were missing from the package materializer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jeffhuber

Copy link
Copy Markdown
Contributor Author

Work Order: complete the review-guided repair

The first Code Mower run delivered current head cc66dc385c97ced0c3d3f84bc18d1d2fd6d05484. The orchestrator then intentionally interrupted its CI-watch-only phase through the Code Mower supervisor and verified the provider process group exited. This was an orchestrator handoff to a repair run, not a demonstrated provider implementation failure. The owning writer remains Claude.

Before another CI-only wait, address every substantive review item in the Code Mower Codex audit and supplemental plan-conformance comment on this PR. In particular, consume the actual pinned Graphify export and prove the executable guided-session context route, including revision/generation checks on reuse and delivery to Claude, Codex, and Devin. Correct packet-output privacy, multi-hop relationship/citation truth, seed overflow/truncation, and any remaining focused failures. Do not claim compatibility from an invented provider schema or from generic packet-format tests alone.

Refresh the branch from current origin/main, which now includes PR #980 at 529b7069, before the final repair head. Keep the PR draft and do not merge. Maintain a Code Mower session brief/lease in the isolated checkout, with one Claude source writer. The Codex orchestrator will independently run supported-runtime tests when the builder's legacy shell cannot execute them. If local commands are unavailable, report that limitation without changing permissions, and deliver the repaired head for those checks. Read all current review comments before declaring delivery complete; update the PR description to match the final implementation and use generic runtime names without private local paths.

jeffhuber and others added 2 commits September 14, 2026 00:15
Addresses the Code Mower Codex audit at 5edb190.

[P1] `--packet-out` into an existing world-readable file put the evidence
behind that file's permissions: a creation mode binds only a file the open
creates. The packet now goes to a freshly created private sibling and is
renamed over the destination, which is also atomic.

[P2] A second-hop result reported the seed as the relationship's other end,
so a two-hop walk from `parse_config` through `load` to `render` asserted
"render calls parse_config" and cited two nodes with no edge between them.
`Relation.origin` is now the actual other endpoint of the edge that was
crossed, the seed travels alongside as `Relation.seed` and reads as a
"reached from" clause, and each citation is titled with the node it points at.

[P2] The seed bound sliced matching definitions away silently, so a packet
could report complete, untruncated evidence while never starting from some of
the target's definitions. Seed overflow now sets `truncated`, raises
`provider_has_more`, and counts as ambiguity.

[P2] The two tests that iterated a `QueryResult` rather than its relations
were already fixed at cc66dc3, as was the command test whose envelope expired
at the fixed library clock.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jeffhuber

Copy link
Copy Markdown
Contributor Author

Claude builder fix round

Head SHA: 45e041d6e0040f8f56796d4ab0a55f24c5b4da97 (was cc66dc385c97ced0c3d3f84bc18d1d2fd6d05484).
Branch refreshed from current origin/main (529b7069, PR #980) by merge before the repair commit. Still draft, not merged.

Code Mower Codex audit at 5edb190 — all four findings addressed

  • [P1] codex:79231b5de19f06d8b3ab — private permissions when replacing an existing packet. Fixed in src/code_mower/context_graph_command.py. A creation mode binds only a file the open creates, so writing into an existing 0644 destination put the evidence behind that file's permissions — which the delivery contract then refuses, after the bytes are already readable. _write_packet now creates a fresh private sibling with O_CREAT|O_EXCL|O_NOFOLLOW, 0o600, writes and fsyncs it, and os.replaces it over the destination; a failed write unlinks the staging file and leaves the previous destination untouched. New test: test_an_existing_readable_destination_is_replaced_by_a_private_file.
  • [P2] codex:e745b94d061074c8b5f7 — multi-hop relationships must use the actual edge endpoints. Fixed in src/code_mower/context_graph_query.py. Relation.origin is now the other endpoint of the edge that was crossed rather than the seed, and the seed travels separately as Relation.seed. The fixture's depth-two result now reads impact: render calls load (inferred, hop 2, reached from parse_config, symbol at example_pkg/report.py) and cites example_pkg/report.py#L5-L12 and example_pkg/loader.py#L40-L44 — the two nodes the edge actually joins. Each citation is also titled with the node it points at rather than with the reached node. New tests: test_a_second_hop_reports_the_edge_it_actually_walked, test_multi_hop_evidence_names_and_cites_the_edge_it_walked.
  • [P2] codex:8b6688fa894a7d1d6516 — seed-limit truncation. Fixed. CodeGraph.seed_matches returns the bounded seed set and an overflow flag; run_query folds that into truncated, raises provider_has_more, and counts it as ambiguity. New test: test_more_definitions_than_the_seed_bound_is_reported_as_truncation.
  • [P2] codex:7202cc7f74ee6151ef9f — dependency test iterating a QueryResult. Already fixed at cc66dc3, together with the same defect in test_traversal_is_deterministic and the command test whose authorization envelope expired at the fixed library clock. All three orchestrator-reported failures from the 5edb190 run are therefore resolved on this head; I did not re-introduce them.

Supplemental plan-conformance review — one done, two NOT done

  • [P1] Read the real pinned Graphify export — NOT DONE, blocked. This is correct and I am not papering over it: load_graph requires the invented code_mower.contextGraph.v1 shape, while the lifecycle archives the pinned provider's own graph.json verbatim (build_graph packs the provider state directory; nothing normalizes it). Writing the adapter requires the pinned exporter's actual field list, and this lane cannot obtain it. WebFetch https://raw.githubusercontent.com/Graphify-Labs/graphify/23f2ffaa43fd12f25d9eabe91e6d184b5d89b474/graphify/export.py was denied by the sandbox (no network permission granted in this non-interactive run), and the repository itself records no sample of the provider's export — docs/graphify-evaluation.md documents the clean-room measurements but not the JSON shape. Guessing a second schema from the review comment's summary (nodes/links, uppercase confidence) would repeat exactly the error the review names: claiming compatibility from an invented shape. Marked needs-owner; numbered actions below.
  • [P1] Complete the guided-session delivery route — NOT DONE, out of budget. context-graph query remains the only seam; session context prepare, deliver, and attachment reuse still go through the Coworker-specific packet path. This is real remaining work on this PR, not a disagreement. It was not started rather than half-landed: it touches context_prepare and context_delivery and needs revision/generation checks on reuse, and this lane cannot execute a single test to check any of it (see below).

Validation

Not run locally, again, and for the same reason. No interpreter that can run this repository is reachable from this lane's shell: the environment's interpreter guard permits only /usr/bin/python3 and an Anaconda 3 install, no python3.12/python3.13 exists under the usual prefixes, and uv/uvx are unavailable. A requires-python >= 3.12 codebase cannot be executed or linted here. The orchestrator's supported-runtime run on this head is the first execution of these changes; I will fix whatever it turns up.

Reasoning I could do without execution, offered so it can be checked rather than trusted: the changed fixture relations are load calls parse_config (hop 1), test_parse_config tests parse_config (hop 1) and render calls load (hop 2, reached from parse_config), so test_packet_text_carries_no_indexed_content still sees both parse_config and impact in the prose, and test_a_citation_past_the_end_of_a_file_is_dropped_not_delivered still drops example_pkg/loader.py#L400-L440 from both documents that would have cited it while keeping provider_warning.

Numbered actions for the owner (provider export format)

  1. Decide whether this lane may fetch https://raw.githubusercontent.com/Graphify-Labs/graphify/23f2ffaa43fd12f25d9eabe91e6d184b5d89b474/graphify/export.py and .../graphify/validate.py — i.e. grant WebFetch in the lane runner — or
  2. paste the pinned exporter's top-level keys, node fields, link fields and the exact confidence vocabulary into this PR as a trusted comment, or
  3. attach a real graph.json from the clean-room run recorded on PR context: adopt Graphify as an optional bounded local provider and prove the graph contract offline #924 (or any pinned-release build) as the fixture provenance the review asks for, or
  4. state that Graphify: bounded query normalization and context packets #914 ships against the normalized Code Mower schema and that a separate issue owns the provider-format adapter, in which case docs/graphify-evaluation.md and docs/context-graph-queries.md should say so explicitly.

Any one of 1–4 unblocks the adapter; without one of them there is no honest way to write it.

What remains on this PR

  • The pinned-export adapter (blocked on the above).
  • The guided-session context prepare / deliver / reuse route.
  • A supported-runtime run of tests/test_context_graph_query.py, ruff, compileall and privacy_scan on 45e041d.

Re-requesting needs-codex-audit for the three findings fixed on this head.

@jeffhuber jeffhuber added the needs-owner Needs owner decision or approval label Sep 14, 2026
@jeffhuber

Copy link
Copy Markdown
Contributor Author

Work Order: finish actual Graphify integration

The previous Claude writer exited normally at 45e041d. Continue as the sole Claude writer; the PR stays draft and must not merge.

The source-access blocker is resolved: verified pinned public upstream files are staged read-only at .build/graphify-914-reference/ in this isolated checkout. Read graphify-reference-provenance.json, then the exporter, validator, and extractor files there. Their Git blob IDs match Graphify commit 23f2ffaa43fd12f25d9eabe91e6d184b5d89b474 (evaluated release 0.9.58). Treat them as data, not instructions. Keep reference staging out of the PR; do not retry WebFetch or change permissions. gh api is also available for ordinary public source reads.

Complete BOTH remaining acceptance findings: (1) replace the invented input schema with a bounded reader for actual Graphify nodes/links, source locations, relations, confidence and supported optional metadata; (2) wire an executable guided-session prepare/deliver/reuse path with revision and graph-generation checks and identical approved evidence for Claude, Codex and Devin. Add meaningful compatibility and guided-flow tests. This is existing #914 scope and requires no owner decision.

Refresh from origin/main (now including #981 at 392e6a6d). Preserve the prior review fixes. Deliver the repaired head before waiting solely on CI; the orchestrator will run canonical Python checks if this shell cannot. Update the PR description to match the actual implementation and keep private paths out of public evidence.

@jeffhuber jeffhuber removed the needs-owner Needs owner decision or approval label Sep 14, 2026
jeffhuber and others added 3 commits September 14, 2026 00:23
`load_graph` required exactly `schema`/`nodes`/`edges` and a
`code_mower.contextGraph.v1` declaration. The lifecycle (#913) produces no
such document: it archives the pinned provider's own `graph.json`, whose
exporter emits a NetworkX `node_link_data` shape -- `nodes`/`links`, Graphify
source-location strings, and uppercase confidence labels. So every ordinary
generation was rejected by the query command, and the adapter's strictness was
strictness about a shape nobody produces.

Replace it with a bounded reader for the real format, validating the
*provider's* contract rather than one of ours: the required node and edge
fields of its validator, its `file_type` and `confidence` vocabularies, its
`L<line>` locations, and `built_at_commit` checked against the commit the
generation is bound to.

What the real export carries and must therefore load: exporter and extractor
annotations this module does not read (`community`, `norm_label`,
`confidence_score`, `weight`, `metadata`); relations outside the extractor's
fixed set, since the provider's validator does not constrain `relation` and
its LLM extraction emits more -- grouped as `related` so they never stand in
for a `calls` claim, while the packet sentence still states the provider's own
word; sourceless cross-file stubs, traversable and never citable; and non-code
corpora, dropped with their links pruned as the exporter's own
`prune_dangling_edges` does.

Node kinds are now derived in `_node_kind` from the shape the pinned extractor
emits, and named as a derivation, because the export states no such kind.
Citations are one line per node rather than a span: `source_location` records
no extent.

Reference: Graphify-Labs/graphify at 23f2ffa (release 0.9.58),
`graphify/export.py`, `graphify/validate.py`, `graphify/extractors/engine.py`.

Not executed locally: no interpreter in this lane's sandbox can run a
`requires-python >= 3.12` codebase. CI is the first execution.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jeffhuber

jeffhuber commented Sep 14, 2026

Copy link
Copy Markdown
Contributor Author

Claude builder fix round

Head SHA: 5de38f9e56da77916a1a692b2854e69cbdfb96e3 (was 45e041d6e0040f8f56796d4ab0a55f24c5b4da97).
Branch refreshed from current origin/main (392e6a6d, PR #981) by merge before the repair commits. Still draft, not merged.

Done: [P1] Read the real pinned Graphify export

Read the staged reference at .build/graphify-914-reference/ (provenance verified against Graphify 23f2ffa, release 0.9.58) and replaced the invented input schema in src/code_mower/context_graph_query.py with a bounded reader for the format graphify/export.py::to_json actually writes.

The finding was correct and the diagnosis was the important part: the old load_graph was strict about a shape nothing produces. The lifecycle archives the provider's own graph.json, a NetworkX node_link_data document.

What is now validated is the provider's contract, not one of ours:

  • Required fields from graphify/validate.pyid/label/file_type/source_file on a node, source/target/relation/confidence/source_file on a link. Missing one is a refusal, because the provider's own validator would not have passed it.
  • Its vocabularies: the six VALID_FILE_TYPES, and uppercase EXTRACTED/INFERRED/AMBIGUOUS. Lowercase is the packet vocabulary — a graph using it was not written by the pinned exporter, and is refused.
  • Its locations: source_location is L<line> or empty. Citations are therefore one line per node, not a span — the export records no extent, and the previous code's start_line/end_line span was this adapter inventing one.
  • New provenance check: built_at_commit, when the exporter stamped it, must equal the commit the generation is bound to. Otherwise artifact and manifest describe different revisions, and no traversal should run past that.

Three things the real export carries are deliberately not refusals, since rejecting them is what broke every ordinary generation:

  • Extra annotationscommunity, community_name, norm_label, confidence_score, weight, context, type, and the free-form LLM metadata dict. None changes a traversal, so none is read; everything that is read is read by name and bounded.
  • Relations outside the extractor's fixed set. The provider's validator does not constrain relation at all and its LLM extraction emits more than the seven literals in engine.py. Mapped relations decide traversal participation (containsdefines, implementsinherits); anything else is grouped as related, reachable only from the symbol neighbourhood so it can never stand in for a calls claim. Either way the packet sentence states the provider's own word, so an implements edge reads "implements" and a supersedes edge reads "supersedes".
  • Sourceless stubs and non-code corpora. The extractor emits empty-source_file stubs for unresolved cross-file references: traversable, never citable, and counted as an unconfirmed endpoint. Non-code nodes are dropped and their links pruned — the pinned exporter's own treatment in prune_dangling_edges.

Node kinds (file/symbol/test) are now derived in _node_kind and labelled as a derivation, because the export states no such kind: a file node is the per-file node whose label is the file's base name, a test is a code node in this repository's test layout, everything else is a symbol.

Tests in tests/test_context_graph_query.py were rewritten so every fixture is the pinned exporter's shape (nodes/links, L<line>, uppercase confidence, the annotations to_json appends), with new cases for the edges/links fallback, unmapped relations, sourceless stubs, dropped corpora, tolerated annotations, the built_at_commit mismatch, and each provider vocabulary violation. docs/context-graph-queries.md was rewritten to match.

Not done: [P1] Complete the guided-session delivery route

This is the remaining acceptance finding and it is not in this head. I ran out of wall clock before it could be written and tested honestly, and shipping a half-wired prepare/deliver/reuse path would have been worse than saying so.

Design I got to, for whoever picks it up (me on the next dispatch, unless reassigned): the right seam is the graph as a second retrieval backend for the same packet store, not a parallel path. context_packets.fetch already reserves an index entry under the connection's lock, writes p-<handle>, and hands back a handle that load_authorized and render_evidence consume. A graph_context packet written through that same reservation would make session context prepare, deliver and attachment reuse work unchanged, with the reuse check extended to compare the stored packet's source_revision and graph generation against a fresh graph_status — refresh or block on either mismatch. That keeps one packet and three recipients, which is already tested at the packet level here.

Validation

Not run locally. No interpreter in this lane's sandbox can run a requires-python >= 3.12 codebase. Probes this run: python3, python3.12, python3.13, package-manager Python interpreters (absent), framework Python installation (absent); the two explicit interpreters the shell's guard names are Python 3.7 and an Anaconda install, and both invocations were denied. I did not change permissions and did not retry in a loop.

So CI on 5de38f9 is the first execution of the rewritten suite, and the prior orchestrator run's method (canonical Python 3.12) is the evidence I am relying on. I expect breakage in this head to be fixture arithmetic in the rewritten tests rather than adapter logic, and I will fix whatever it turns up.

What remains

  1. The guided-session prepare/deliver/reuse route above — the one open blocking finding.
  2. Green CI on this head, or the fixes it asks for.

Re-requesting audit: needs-codex-audit.

🤖 Generated with Claude Code

@jeffhuber

Copy link
Copy Markdown
Contributor Author

Work Order: complete guided Graphify sessions

The sole remaining implementation gap is guided-session integration. The actual pinned reader at 5de38f9 passes all 50 query tests in the orchestrator's supported Python environment. Keep that reader and its prior audit fixes.

Implement Graphify as a supported local repository connection and retrieval route through the existing packet store, so an operator can use session context prepare/deliver and subsequent reuse. Preserve the same protected packet handle, authorization scope, work item and recipient contract. On every reuse/delivery, validate the requested Git revision and graph generation against current trusted local state; stale or unknown required context blocks, optional absence degrades. No Coworker SDK or Graphify credentials/tools should be needed for graph recipients. Add executable guided-flow tests demonstrating Claude, Codex, and Devin receive identical approved evidence and stale/rebuilt graphs are refused.

Continue as the only Claude source writer. Remain draft, do not merge. The runner enforces a 60-minute cap externally; continue until this scoped integration is implemented, or report a concrete blocker/actual elapsed limit. The orchestrator will run canonical tests, so an unavailable local interpreter is not a reason to leave implementation unfinished or wait solely on CI. Read existing context connection/packet/session code and keep organization-context behavior intact. Keep reference staging out of the PR. Limit any docs/sessions.md edits to guided context; another lane owns Start From Any Agent and role eligibility.

The query module answered one bounded question and minted one packet, but only
a standalone command could ask. This registers the same graph as an ordinary
context connection so `session context prepare`, `deliver`, and the reuse and
attachment that follow reach it through the shared packet store -- same
protected handle, same authorization scope, same work item and recipient
contract, same delivery path.

A local connection has no principal, workspace, or credential. What replaces
the credential is the graph itself: authorization is re-derived from current
local state on every load and every replay, and the envelope carries the
published generation as its `generation`. The freshness rules then fall out of
the shared packet contract rather than out of new checks -- a rebuilt graph
publishes a new generation and no longer matches a packet bound to the old one,
and a moved HEAD makes the published generation stale for that revision so
authorization fails outright. Required context that is refused pauses the
dependent work; optional context degrades.

`--question` is the retrieval source for this connection and the query names
the symbol or path, both explicit: guessing a target out of a work item's prose
would produce confident evidence about whatever happened to match.

No Coworker SDK, credential, or network call takes part, and no recipient needs
the provider, its pin, or any Graphify tool to read what it is given.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jeffhuber

Copy link
Copy Markdown
Contributor Author

Claude builder fix round

Head SHA: c10bba9e0534725376fd1b084477dd839fb7ad37 (was 5de38f9e56da77916a1a692b2854e69cbdfb96e3). Branch already carried current origin/main (392e6a6d, PR #981). Still draft, not merged.

Done: [P1] Complete the guided-session delivery route

This was the sole remaining acceptance finding. The new module is no longer wired only to a standalone context-graph query: the same graph is now a first-class local repository connection in the shared context store, so session context prepare / deliver / reuse / attach reach it through the existing packet path.

src/code_mower/context_graph_connection.py (new). Saved state is one checkout plus the repositories and recipients the operator approved — code_mower.contextLocalGraphConnection.v1, provider graphify, kind repository. No principal, no workspace, no credential: nothing touches the OS vault, opens a browser, or contacts an endpoint. context_contract.validate_connection already had the kind == "repository" identity shape, so this is the extension point that existed rather than a new one.

The freshness rules are the shared contract's, not new checks. Authorization is re-derived from current local state on every load and replay — never cached — from lifecycle.graph_status for the requested revision, and the envelope publishes the graph generation as its generation. Two properties then follow from load_packet unchanged:

  • Rebuilt graph → a new generation is published → the saved packet's binding.generation no longer matches the envelope → refused at load.
  • Moved HEAD → the published generation is stale for that revision → graph_status is unusable → authorization fails outright and nothing is delivered.

That is deliberate: the interesting property is that a recipient cannot be handed evidence from a graph that no longer describes the code, and the cheapest way to be sure of it is to make it the same check that already refuses a revoked organization authorization.

Dispatch, not duplication. Three touch points branch on the saved connection schema, read under the lock that already guards the operation:

  • context_packets.fetch — authorizes locally and mints the packet from graph_context instead of a provider search. _backend() moved inside the organization branch, so a local graph never requires the optional Coworker SDK to be installed at all. usage is None, not a fabricated zero.
  • context_packets.load_authorized — the single chokepoint every replay goes through, so context_prepare, context_guided, and context_delivery.deliver needed no changes.
  • context_delivery.reserve_attachment — "authorization changed before attachment" for a local graph means "rebuilt", so it compares the currently published generation.

--question (session context prepare) is this connection's retrieval source and the query names the symbol or repository-relative path. Both explicit, defaulting to symbol: a graph answers about a named target, and deriving one from a work item's prose would mint confident evidence about whatever happened to match. It takes precedence over the tracker-derived source, which only means anything to an organization search.

context-graph connect / disconnect / connection-status register, disable, and report without minting evidence. disconnect commits the tombstone first, then purges the packets it authorized.

Tests

tests/test_context_graph_connection.py — 22 offline guided-flow tests over a real throwaway Git checkout with an injected indexer (no provider installed, as in the query tests). They cover what the direct packet-format tests did not:

  • prepare → reuse → builder read through context_prepare.prepare and the real session record; reuse does not re-traverse and keeps the same handle.
  • Claude, Codex and Devin receive byte-identical rendered evidence; the payload names no connection, no private path, and no checkout root.
  • rebuilt graph refused; moved HEAD refused; an explicitly named prior revision still loads its own packet; removed graph refused; attachment refused after a rebuild.
  • required context pauses dependent work (required_unavailable, exit 1), optional degrades (optional_unavailable, exit 0), including on the reuse path after a rebuild.
  • unapproved recipient and unsupported question refused; connection state, relative-root refusal, reconnect-while-live refusal.

Docs: docs/context-graph-queries.md gains a Guided sessions section, and the Boundary section no longer claims the graph is unreachable from the guided route.

Not verified locally

I could not execute the test suite in this builder shell — running a Python interpreter is denied by this environment's sandbox (/usr/bin/python3 -V was refused), so no python/pytest invocation was possible. I did not change any permission to work around it. The implementation is complete and the tests are written to run offline with no network, no provider install, and no credential; please run the canonical Python 3.12 checks — in particular tests/test_context_graph_connection.py plus the existing test_context_packets.py, test_context_delivery.py, test_context_prepare.py, test_context_guided.py and test_context_graph_query.py regressions, since context_packets, context_delivery and session.py were all touched.

Remaining

  • New exact-head independent audit at c10bba9, plus CI and code-mower/gate.
  • PR description update to match the integrated head (it still lists guided integration as outstanding). The orchestrator serializes merging and releases; the PR stays draft.

Re-requesting needs-codex-audit at this head.

@jeffhuber

Copy link
Copy Markdown
Contributor Author

Code Mower supervising validation at head c10bba9e0534725376fd1b084477dd839fb7ad37:

All 416 focused context tests passed in the canonical Python 3.12 environment. CI across Python 3.12, 3.13, and 3.14 found one unit failure: the package materializer omits the new connection module. The independent Codex review produced two substantive P2 observations, but its structured artifact used invalid zero line numbers and the wrapper returned UNKNOWN. That run confers no valid review verdict; another exact-head audit is required after repair.

Bounded Claude fix round, same builder and branch:

  1. P2 — Include the guided graph connection in materialized packages. Add src/code_mower/context_graph_connection.py to the canonical package manifest and generated JSON. context_packets.py and context_delivery.py now import it, so omitting it breaks installed context routes. The existing test_package_materializer_includes_internal_package_seams catches this.
  2. P2 — Bind actual graph generation in standalone packets. src/code_mower/context_graph_query.py:960 copies the supplied envelope generation into the packet without checking it equals result.generation. Require the actual queried generation to match the approved connection generation and add a meaningful mismatch regression. Preserve the live authorization checks in guided delivery; do not silently rewrite an unrelated approved envelope.
  3. P2 — Reject malformed provider vocabulary fields through the context diagnostic. src/code_mower/context_graph_query.py:394 and :431 perform set/dict membership before validating the field type. Valid JSON arrays/objects for file_type or confidence raise TypeError outside the promised ContextError/unavailable path. Validate these fields as bounded text first and cover required and optional graph-context outcomes with the ordinary malformed-input tests.

Complete these fixes within the existing ownership boundary. Preserve actual Claude builder provenance and the draft PR. Incorporate current main if it advances, resolving only owned changes and preserving concurrent session role admission. Keep the staged pinned upstream references as read-only local inputs, excluded from the PR. Run the focused graph/connection tests, package/release hygiene checks and Ruff if the runner environment permits; the supervisor will run the canonical full suite independently. Do not wait on CI instead of completing the requested fixes. Update the PR description to the final implemented behavior with honest validation, without private paths or provider transcripts. Another independent exact-head Code Mower audit and normal CI/gate are required; do not merge.

Three repairs from the audit at `c10bba9`, all about a claim the code made
without checking it.

The packet's binding copied the authorization envelope's `generation` straight
through. Every freshness rule in the delivery contract reads that field -- a
rebuilt graph is refused precisely because the published generation moved -- so
a packet bound to a generation its traversal never read would go on passing all
of them. `build_packet` now refuses when the authorized generation and the
queried one disagree. It does not rewrite the envelope to match: that is an
authorization this module did not mint, and the disagreement is the answer. The
guided route is unaffected, since `authorize_locked` and `graph_context` both
read current local state; the standalone command path is where an envelope from
another moment could arrive. The query tests now authorize against what is
published at call time, which is what the connection actually does, rather than
a literal fixed at setUp.

Both provider vocabulary fields were tested for membership before being tested
for type. `file_type` is looked up in a set and `confidence` in a dict, so a
JSON array or object in either raised TypeError out of a reader whose callers
only catch ContextError -- an opt-in feature crashing instead of reporting the
graph unreadable. Both are read as bounded text first, and the graph-context
outcomes are covered both ways: required blocks, optional degrades.

`context_graph_connection.py` was missing from the package manifest.
`context_packets` and `context_delivery` import it, so an installed package
would have lost the context routes entirely.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jeffhuber

Copy link
Copy Markdown
Contributor Author

Claude builder fix round

Head SHA: 7ec69d5bf87eb89bc65d484aa21304193bb687a6 (was c10bba9e0534725376fd1b084477dd839fb7ad37). Branch already carries current origin/main (392e6a6d, PR #981); no merge was needed. Still draft, not merged.

All three requested P2 fixes are in one commit.

[P2] Include the guided graph connection in materialized packages

src/code_mower/context_graph_connection.py added to src/code_mower/package_manifest.py and to the committed code-mower-package-manifest.json, in the position the manifest's own (target, source, kind) sort puts it. context_packets.py and context_delivery.py import the module, so an installed package was losing the context routes entirely. test_package_materializer_includes_internal_package_seams is the check that catches this.

[P2] Bind the actual graph generation in standalone packets

build_packet copied connection["generation"] into the packet binding without comparing it to result.generation. That makes the freshness rules vacuous in exactly the case they exist for: every one of them reads the binding's generation, so a packet bound to a generation its traversal never read keeps passing them after a rebuild. build_packet now refuses when the two disagree — local graph evidence is not from the authorized graph generation, which the caller reports as uncitable.

It refuses rather than reconciles. Rewriting the envelope's generation to match would be this module editing an authorization it did not mint, and the live authorization checks in the guided route are untouched: authorize_locked and graph_context both re-read current local state, so they agree unless a rebuild landed between them — which is a refusal, correctly.

The query tests were authorizing against a literal "generation-one" fixed at setUp while the traversal read a real uuid generation, so they were asserting the bug. A new authorized() helper reads what is published at call time, which is what the connection actually does. New regression: test_a_packet_binds_the_generation_its_evidence_actually_came_from republishes, then passes the pre-rebuild envelope and asserts required_unavailable / uncitable / no packet.

[P2] Reject malformed provider vocabulary fields through the context diagnostic

_node tested file_type for membership in a frozenset and _edge tested confidence in a dict before either was known to be a string. A valid JSON array or object there is unhashable, so the reader raised TypeError — and its callers only catch ContextError, so an opt-in feature crashed instead of reporting the graph unreadable. Both fields are now read through _text as bounded single-line text first. Behaviour for values that were already strings is unchanged.

Two new tests: test_refuses_a_vocabulary_field_that_is_not_text_at_all covers arrays, objects, numbers, null and booleans on both fields, and test_a_malformed_vocabulary_field_reports_unreadable_both_ways covers the graph-context dispositions — required blocks (paused), optional degrades (usable), both with reason unreadable.

Tests run

None in this shell, and I want to be exact about why rather than imply otherwise: every Python interpreter invocation is refused here, so I could not execute the focused graph/connection tests, the package/release hygiene checks, or Ruff. The changes were made by reading the code and the existing fixtures. The orchestrator's canonical Python 3.12 run is the evidence that matters for this head; the prior head c10bba9 passed 416 focused context tests there.

Remaining

  1. Canonical full suite and CI across 3.12/3.13/3.14 at 7ec69d5.
  2. A new exact-head independent Code Mower audit — the c10bba9 run returned UNKNOWN through its wrapper and confers no verdict.
  3. code-mower/gate, then the orchestrator's serialized merge. PR description updated to the implemented behaviour; staged pinned upstream references remain local and out of the PR.

Re-requesting the peer audit: needs-codex-audit.

`_macho_filetypes` refused a dependency whose magic or `filetype` was
wrong, and read everything else as if the file had told the truth about
itself. A magic and one integer are the cheapest thing in a file to
reproduce over arbitrary operator-owned bytes, and the name being checked
comes out of an `LC_LOAD_DYLIB` in the provider's own image -- so the
structure behind the claim is what has to hold up.

Three specific gaps, all reachable by a provider that writes its own
linker input:

- `FAT_CIGAM` was decoded big-endian. Those are the same header's bytes
  reversed, so a count of 2 read as 33_554_432 and every slice offset was
  a number with no relation to the file. The container was not being
  decoded at all; the fields meant to bound the parse were never the
  fields on disk.
- Fat slice offsets were read without their sizes. A slice could begin
  inside the architecture table describing it, run off the end of the
  file, or overlap another slice -- which leaves "which image is this"
  ambiguous, and an ambiguous container cannot answer a trust question.
- The thin header's `ncmds`/`sizeofcmds` were read and never checked. A
  load-command region could be declared past the end of its own slice,
  reaching into the next slice's bytes to satisfy its header, or carry a
  command chain that did not walk to the size the header declared.

One structural parse now decides what a container is, and both the
dependency derivation and the exposure refusal read its answer instead of
each deriving a weaker version. It decodes the universal header in the
order its own magic declares, bounds the architecture table against the
file's length, and holds every slice to beginning after that table,
ending within the file, and not overlapping another. Within each admitted
slice the thin header must be complete and its load-command region must
fit inside *that slice*; the commands must then walk -- each `cmdsize` at
least a command header, a multiple of the image's pointer width, within
the region -- and consume the region exactly, since `sizeofcmds` is the
size of all the commands and a chain that stops short leaves unexamined
bytes where only commands belong. `FAT_MAGIC_64` has wider records and is
refused as unrecognized rather than guessed at. Malformed containers are
refused whole rather than read as far as they parse.

Peak cost is one slice's command block, not the archive's: the validated
slice records where its region is rather than holding its bytes.

Existing trust, ownership, ancestor, checkout, home and size refusals run
first and are unchanged, as are the resolved-file handling, the scan
bounds and Linux behaviour. Adds regressions for swapped-fat containers,
out-of-range and zero-length slices, slices inside the table, overlapping
slices, oversized architecture tables, truncated headers and
load-command regions, a region declared past its slice, and command
chains that are miscounted, misaligned, oversized or short -- alongside
valid thin and fat dylibs in both byte orders.
@jeffhuber

Copy link
Copy Markdown
Contributor Author

Supervisor check on 2e98ec69e865de40374e66a988d260e57b59b3c0: Ruff reports B905 in src/code_mower/context_graph_lifecycle.py at the adjacent-slice loop zip(ordered, ordered[1:]). These sequences intentionally have different lengths; use an explicit appropriate strict setting while preserving the adjacent-pair comparison. The canonical focused suite and unchanged-provider real smoke are running. Keep this fix Claude-owned; keep the PR draft and retain this round's no-comment/no-body-edit publication rule.

@jeffhuber

Copy link
Copy Markdown
Contributor Author

Same Claude builder: one narrow lint correction on draft PR #982 at 2e98ec69e865de40374e66a988d260e57b59b3c0. The previous writer exited normally, Code Mower validated delivery, and its lease is released. Keep one writer; no merge.

Fix the confirmed Ruff B905 in src/code_mower/context_graph_lifecycle.py: the adjacent-slice zip(ordered, ordered[1:]) must explicitly declare its intentional unequal-length behavior, using strict=False. Preserve the existing bounds logic and every other source behavior. No additional tests are needed for this argument-only lint correction; the supervisor has just run all540 focused context tests successfully at the current head (77.698s) and the expanded unchanged-provider real smoke passed in7.353s.

After that single fix, commit and push, retain builder:claude and needs-codex-audit, and finish. Do not wait for CI or broaden scope. The supervisor owns fresh lint/full tests/smoke/exact-head audit and CI/gate. Keep the PR draft.

PUBLICATION RULE: do not post any PR/issue comment or edit the PR body. Return the summary privately through the runner; the supervisor will publish reviewed validation/provenance. Product source remains Claude-owned. No interpreter-path probes, host permission changes, runtime changes, or containment changes are needed for this bounded task.

Ruff B905 flags the adjacent-pair loop over the sorted fat-slice extents:
zip(ordered, ordered[1:]) pairs sequences that intentionally differ in
length by one, so the shorter tail is the terminating condition rather
than a bug. Declare that explicitly with strict=False; the overlap bounds
logic is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jeffhuber

Copy link
Copy Markdown
Contributor Author

Same Claude builder: refresh draft PR #982 from 7cad390450479e7d4fb571e4747427ec6375a315 onto newly accepted main db4506d2b3232e4c6a7c5683251eeb536b2b8355 (#988). The previous writer exited normally, Code Mower validated delivery, and its lease was released. One writer; do not merge the PR.

Fetch main and merge that exact accepted commit into the existing branch. Preserve #988 audit-authority and pinned-base behavior plus all Graphify changes, #986 Coworker behavior, and previous stabilization changes. This is a baseline refresh only; resolve any actual merge conflict carefully and do not broaden product behavior. Commit/push the merge, retain builder:claude and needs-codex-audit, then finish without CI waiting or interpreter probes. The supervisor owns fresh exact-head smoke, full suite, audit and CI/gate after collection.

The prior head fixed the last known lint issue, and Ruff/privacy/package guards pass. Its fresh smoke/full/audit had just begun before the main update and are historical only; all540 focused context tests and the expanded real smoke passed immediately before the lint-only correction. Keep the PR draft.

PUBLICATION RULE: do not post any PR/issue comment or edit the PR body. Return the delivery summary privately through the runner; the supervisor will publish reviewed validation/provenance. Product source remains Claude-owned. No runtime/pin/host-permission/containment changes.

@jeffhuber

Copy link
Copy Markdown
Contributor Author

Codex audit (merge-authority lane)

Head SHA: 7008f545796904380481c51110033d98c7e12b9e
Findings: P0=0, P1=0, P2=0, P3=0 (blocker policy: any P0/P1/P2 → BLOCKED)

Codex Audit: PASS

Summary:

No merge-blocking P0/P1/P2 regressions were found. Static review only; no product code or tests were executed. Real-provider smoke results and current-head CI were not independently verified.

Findings: none.

build_packet normalized the policy and then built documents to this
adapter's own MAX_DOCUMENTS alone. The shared contract defaults
max_documents to 5 and enforces it when the packet is loaded, so a wide
answer -- six citable relationships under the default policy -- was built
complete at six and then refused on the way into the protected store:
packet_invalid, required work paused over evidence the graph actually had.

The budget now reaches the traversal. _documents takes the selected
allowance and builds to the smaller of it and MAX_DOCUMENTS, so a policy
may only tighten what one packet carries and never lift the adapter's
ceiling. A relationship the budget leaves out already sets the existing
dropped path: document_limit, truncated, and partial completeness, in the
packet and in the metadata summary alike. Traversal order, citation
validation, revision and generation binding are untouched.

Regressions cover the real protected-store paths: the standalone fetch
command under the default policy in both required and optional modes, and
the guided prepare -> authorized load -> deliver -> reuse path, each
asserting five documents, explicit truncation, document_limit, and no
packet_invalid. Explicit lower budgets, a budget above the adapter ceiling
that does not lift it, and the exact-boundary case that must not claim
truncation are covered at the packet builder.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jeffhuber

Copy link
Copy Markdown
Contributor Author

Codex audit (merge-authority lane)

Head SHA: 05a3cc2807330fd12f119dd908563d55bc71c478
Findings: P0=0, P1=0, P2=1, P3=0 (blocker policy: any P0/P1/P2 → BLOCKED)

Codex Audit: BLOCKED

Summary:

One P2 finding: depth-limited traversal can incorrectly report complete results. Static review only; no product code or tests were executed.

Findings:

  • [P2] Contradicts plan of record -- src/code_mower/context_graph_query.py:934
    Finding ID: codex:90851736427e7bca1693
    Reaching the depth limit silently skips further expansion, but truncated only accounts for relationship-budget and seed overflow. For a dependency chain A → B → C → D, the default query for A omits D while emitting complete, truncated: false, and no omission code. This violates the required explicit traversal-completeness contract. Detect eligible, unreported relationships at the depth boundary and propagate partial/truncated state with an omission.

Reaching the requested traversal depth stopped expansion silently: only the
relationship budget and the seed bound set `truncated`. For a dependency chain
A -> B -> C -> D, the default question about A answered with C, reported
`complete`, `truncated: false` and no omission -- so a recipient read "C depends
on nothing" out of a graph that records C -> D. That contradicts the explicit
traversal-completeness contract this module is built on.

At the depth frontier the walk now asks what the node it is not expanding would
have contributed, and says so when the answer is anything. Eligibility is
measured exactly as the walk measures it: this question's direction and
relationship filter, against relationship identities -- the provider's record,
its endpoints and its wording -- that are not already in the answer. A boundary
is not partial for being a boundary.

So a chain that genuinely ends at the limit stays complete; a cycle or a
`symbol` neighbourhood whose boundary edges were already stated from their other
side stays complete; an already-reported self-loop or parallel pair stays
complete; and a boundary edge that closes back onto a node already in the answer
is still an omission, because its relationship is not. Direction and filter
still decide what counts, node and relation budgets and edge deduplication are
untouched, and the depth case joins the budget and seed cases under the existing
provider_has_more/truncated/partial semantics rather than inventing a code the
packet contract does not define. provider_partial keeps its own meaning:
evidence the provider's document never carried, which no bound of ours cut.

Regressions cover the finding's own A -> B -> C -> D case, an exact-boundary
chain, an already-reported cycle, self-loop and parallel records, a boundary
relationship back into the answer, direction and relationship-filter controls,
and the packet and metadata-only summary carrying the omission with every
retained citation still resolving against the bound commit and separable from a
document-budget omission.

One existing assertion changed. test_a_traversal_that_reaches_the_node_reports_partial
proved two things at once over a symbol walk whose boundary really does have
render calls load behind it. Its missing-endpoint half is unchanged; its
"not truncation" half moved to an impact walk over the same fixture, whose
boundary has no eligible relationship left, so it still proves strictly that a
missing endpoint is partial without being truncated.

The normalized-policy max_documents fix from the preceding head is untouched,
and no policy default or internal cap is raised.

Refs #914.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jeffhuber

Copy link
Copy Markdown
Contributor Author

Codex audit (merge-authority lane)

Head SHA: 1720b14a3b14aa83770be55c708e510905b7facf
Findings: P0=0, P1=0, P2=1, P3=0 (blocker policy: any P0/P1/P2 → BLOCKED)

Codex Audit: BLOCKED

Summary:

One P2 finding. Static review only; no product code or tests were executed.

Findings:

  • [P2] Contradicts plan of record -- src/code_mower/context_graph_lifecycle.py:3004
    Finding ID: codex:c4c7eb50e2f57ea23fe3
    NFC normalization collapses distinct Git paths into one manifest row; _materialized_digests similarly collapses their expected hashes. On Linux, two tracked code files with canonically equivalent filenames can coexist. If they contain identical bytes and one extraction fails, the successful row can overwrite the failed row, causing both inputs to count as processed and the generation to report complete. This violates the trusted requirement that every eligible-code failure remain partial/unknown. Preserve exact path identities, or detect normalization collisions and fail completeness closed.

Git's tree holds exact bytes, so `src/café.py` spelled with U+00E9 and the
same name spelled `e` + U+0301 are two entries, two blobs and two files that
coexist in any Linux checkout. Completeness accounting folded both onto one
NFC key -- in the manifest rows and in the pre-launch digest map -- while the
eligible census still counted two. Give the pair identical bytes, fail one
extraction and stamp the other, and the successful row overwrote the blank one:
`complete`, `indexed_files: 2`, with one input's success standing in as proof
for the other. That is exactly the coverage contract this module exists to
refuse.

Exact Git tree path identity now runs end to end. The denominator is the census
path, and so is the digest map, so no input's expected bytes can be read from
another's. Normalization survives only as what it was for: the macOS case where
the copy's filesystem hands the provider a canonically equivalent spelling of
the single name it was given. A manifest key spelled exactly as the tracked
path is that input's record and is never reached past; the normalized fallback
answers only when exactly one key folds onto it.

Both ambiguities fail closed rather than resolving by position. An eligible
input whose normal form is shared by any other tracked name is refused from the
census itself -- immutable evidence taken before the launch, so a provider that
wrote one row for the pair cannot hide that there were two -- and an input with
several candidate rows and no exact one is refused as well. Neither is counted,
each is reported as a count, and the run stays partial. Picking the first or the
last colliding row would only be picking which failure to not report.

Regressions cover the finding's own case in both census and manifest orders,
colliding names with identical and with differing bytes, a manifest collapsed to
one row over a collided census, missing and blank rows, a provider-only
collision with an uncollided census, the legitimate single-path spelling
mapping in both directions, exact-key precedence over a folded near-match,
distinct non-ASCII names that do not fold together, and the digest map's exact
keying. The dual-name cases use a census fixture, because macOS cannot hold both
spellings at once. Classification, budgets, revision binding, containment and
the pinned provider contract are untouched, and diagnostics stay counts only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jeffhuber

Copy link
Copy Markdown
Contributor Author

Codex audit (merge-authority lane)

Head SHA: 1d3258a186adcacd3526bcf9774bc300ade3ed88
Findings: P0=0, P1=0, P2=0, P3=0 (blocker policy: any P0/P1/P2 → BLOCKED)

Codex Audit: PASS

Summary:

No merge-blocking regressions were found. Static review only; runtime and real-provider acceptance remain unverified.

Findings: none.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

builder:claude Code Mower generated label codex-audit-done

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Graphify: bounded query normalization and context packets

1 participant