feat(doc-index): infer real section granularity, hash sections for staleness - #1
Open
tkcoding wants to merge 8 commits into
Open
feat(doc-index): infer real section granularity, hash sections for staleness#1tkcoding wants to merge 8 commits into
tkcoding wants to merge 8 commits into
Conversation
…uctorfabric#105) Token-overlap pre-filter surfaces weak links between a marked block and the requirement it cites; an injected SemanticJudgeFn rules only on those (covered/partial/wrong), advisory and never gating. Honest by construction: unjudgeable-not-zero, an evidence guard on every quote, and full per-block accounting (assessed + presumed_covered + unjudgeable). Library + tests; report integration is the follow-up. Signed-off-by: Sanjeev Solanki <sanjeev.solanki@constructor.tech> Co-authored-by: Sanjeev Solanki <sanjeev.solanki@constructor.tech>
…ction is visible (constructorfabric#121) * ci: run the enforcement gate corpus as its own check The gates that reject an unbacked claim are exercised only in their passing direction by the pipeline. `ci.yml` has ten jobs and none of them asserts that a seeded violation is rejected, so a green run is evidence that the checks ran, not that they can fail. The project's own finding applies to its CI as much as to the commands: a gate that never fails is worthless, and nothing here demonstrates otherwise. The assertions already exist. `tests/test_enforcement_empty_codebase.py` and `tests/test_enforcement_empty_scan.py` drive the real CLI and assert exit codes in both directions -- a registered codebase holding one unmarked source file exits 2 with `code-no-marker`, a correctly marked implementation exits 0, and a repository that claims nothing stays green with no code at all. What they lack is visibility: they run inside a suite of ~4,850 tests, so the red direction is provable only by knowing the tests exist and reading them. `make test-gates` runs those two files alone, and a `gates` job surfaces the result as a named check. No new tests and no behaviour change -- this makes an existing guarantee legible in the place people look for it. Verified the job is load-bearing rather than decorative: restoring the pre-existing early return in cross-validation (`if not results.parsed_code_files_full:`) fails 5 of the 39, so the check catches the regression it exists to catch. Green on `main` as it stands. Deliberately scoped to the enforcement corpus. The semantic-coverage tests are advisory and have an open interpreter-dependent failure of their own, so folding them in would make this check red for a reason unrelated to enforcement. Signed-off-by: ou <ou@constructor.tech> * ci: drop the job token from the gates checkout The gates job runs pull-request code (`make install`, `make test-gates`). `actions/checkout@v4` defaults `persist-credentials` to true, which writes the job token into `.git/config` where that code could read it back. Set `persist-credentials: false` and pin the job to `contents: read`, matching the least-privilege job-level `permissions` block already used in code-ranker.yml. Scoped to the job this PR adds. The nine pre-existing checkout steps share the same default and are left for a separate decision. Signed-off-by: ou <ou@constructor.tech> --------- Signed-off-by: ou <ou@constructor.tech> Co-authored-by: ou <ou@constructor.tech>
tkcoding
force-pushed
the
jit-retrieval-cascade
branch
2 times, most recently
from
September 1, 2026 03:50
bd02778 to
2af77ae
Compare
tkcoding
force-pushed
the
jit-retrieval-doc-index
branch
from
September 1, 2026 05:07
f877490 to
2a60188
Compare
tkcoding
force-pushed
the
jit-retrieval-cascade
branch
2 times, most recently
from
September 1, 2026 05:18
30b0736 to
3493f4b
Compare
…c index (constructorfabric#108) Squash-merged after Deep Review: all 5 Major findings verified fixed (human-output warning visibility, cache shape validation, mtime+size etag documentation, duplicate-heading normalization, correct @Cpt-Flow traceability tag). 12 Minor findings remain as non-blocking follow-ups. CI green across all 23 checks.
…aleness Neither of these existed before: doc_index.py indexed every heading at every level, with no notion of "one retrievable section", and its staleness check was whole-file only -- any edit anywhere invalidated the entire cached index, making a real per-section partial rebuild impossible regardless of how small the actual edit was. infer_section_level() picks which heading level represents one real section, using the level's *frequency* as the signal: a document's real recurring structure (its chapters) shows up as the level used most often, while an occasional heading at an anomalous level -- exactly what PDF-to-Markdown conversion produces, since it assigns levels by font-size heuristics, not semantic depth -- is rare precisely because it's noise, not structure. Levels used only once are excluded as candidates outright. This is a direct, verified fix for a real failure found earlier building this feature: a real PDF-converted document put all 8 of its actual chapters on H5 and a single stray subsection on H3; treating H3 as "the" section level (or any fixed level) turned the entire back half of the document into one fake 6,601-line "section". Re-run against that same document with this change: 12 correctly-sized real sections, not one. build_doc_index() now also computes retrieval_sections -- headings grouped at exactly the inferred level (off-level stray headings stay inside whichever section they geographically fall under, rather than splitting one apart), each with a SHA-256 hash of its own text. diff_stale_sections() compares a file's current content against its last cached build at this granularity and reports which sections actually changed, matched by position (not heading text -- duplicate titles are real, see toc-heading-duplicate) -- the piece needed for a future caller to re-summarize only what changed instead of the whole document. Registered the three new instructions in traceability-validation.md; whitelisted diff_stale_sections in vulture_whitelist.py alongside annotate_section_summary (same "future caller, exercised by tests" situation). New code is 100% covered; existing sections/annotate/etag behavior is untouched and still passing. See constructorfabric#104. Verified: pytest (test_doc_index.py + test_toc.py: 156 passed; full suite: 4815 passed, the same 12 pre-existing macOS-local/flaky failures as on main, none in the files touched here), pylint and vulture clean, cfs validate 0 errors, spec-coverage thresholds met, and infer_section_level re-run against the real PDF-converted document that originally exposed the bug. Signed-off-by: TECK KEAT WILSON <yeow.teck.keat@constructor.tech>
…abric#109 - cmd_doc_index() built its output from the index but omitted retrieval_sections/section_level in both JSON and human output -- the new data constructorfabric#109 added was invisible through the CLI. Both are exposed now, and the human formatter lists retrieval sections the same way it already lists the finer-grained ones. - A write landing between read_text() and _compute_etag() in build_doc_index() could save headings parsed from the *old* content stamped with the *new* file's etag; load_doc_index() would then treat that stale index as valid until a later edit changed the etag again. _read_with_stable_etag() brackets the read with a stat snapshot on each side and retries on mismatch, so the saved etag is provably the one that matches what was actually parsed. - diff_stale_sections() reported changed/unchanged sections by heading text alone; two sections sharing a duplicate title (a real, already-flagged possibility -- see toc-heading-duplicate) couldn't be told apart. Each entry now carries line_start alongside the heading text, which is what a caller should actually use to address "this specific section" afterwards. - annotate_section_summary() updated only index["sections"], leaving the matching retrieval_sections entry at summary=None even on success -- a caller reading retrieval_sections (the more relevant list for a future per-section summarizer) couldn't see the annotation. Now updates both when both have an entry at line_start. - toc.py's _frontmatter_has_description() accepted `description: # TODO` and `description: ""` as satisfying the check, since `#` and `"` both match \S. Now parses the field's actual value and rejects comments and empty/whitespace-only quoted strings. Extracted _compute_fresh_retrieval_sections/_position_entry out of diff_stale_sections() to stay under pylint's local-variable limit after the line_start addition; registered the new instructions (stable-read, diff-stale-helpers) in traceability-validation.md. See constructorfabric#104. Verified: pytest (test_doc_index.py + test_toc.py: 166 passed, 100% coverage on touched doc_index files); full suite: 4825 passed, the same 12 pre-existing macOS-local/flaky failures as on constructorfabric#108/constructorfabric#109, none in files touched here; pylint and vulture clean; cfs validate 0 errors; spec-coverage thresholds met; infer_section_level/retrieval_sections re-verified against the real PDF-converted document that originally exposed the granularity bug -- still 12 correct sections. Signed-off-by: TECK KEAT WILSON <yeow.teck.keat@constructor.tech>
…torfabric#109 - load_doc_index() returned a cached index whenever its etag matched, with no check that the cached shape matched what this version of the code expects. A cache written before section_level/retrieval_sections existed can still have a matching etag if the file hasn't changed since -- cmd_doc_index() would then hit a KeyError reading those fields on a legacy cache instead of a clean rebuild. Now treated the same as a stale cache: rebuilt, not returned as-is. - _frontmatter_has_description() treated a YAML block-scalar marker (`description: |`, `description: >-`, ...) as a usable value, when the real content -- if any -- belongs on indented lines below it, not on the marker's own line. Now checks the first non-blank following line for real indentation before counting it as a description. See constructorfabric#104. Verified: pytest (test_doc_index.py + test_toc.py: 172 passed, 100% coverage on touched doc_index files); full suite: 4831 passed, the same 12 pre-existing macOS-local/flaky failures as before, none in files touched here; pylint and vulture clean; cfs validate 0 errors; spec-coverage thresholds met; infer_section_level/retrieval_sections re-verified against the real PDF-converted document -- still 12 correct sections. Signed-off-by: TECK KEAT WILSON <yeow.teck.keat@constructor.tech>
…on PR constructorfabric#109 - doc_index.py: documented the additive-only schema contract (new fields need no version bump; only an existing field's meaning/shape changing does); non-UTF-8/binary input read failures are now caught and either degrade gracefully (diff paths) or propagate as a typed, catchable UnicodeDecodeError (build paths); per-section hashing now strips trailing whitespace per line, so an editor's "trim trailing whitespace on save" no longer looks like a real content edit; three genuinely abnormal cache fallback paths (directory lookup failure, corrupt cache file, source file vanishing mid-check) are now logged at WARNING, visible at the CLI's default log level, while the one path with a documented, legitimately expected trigger (diffing against a since- deleted file) stays at debug; annotate_section_summary's read-modify- write cycle now runs under an exclusive file lock (mirroring decision_log.py's established fcntl pattern), closing a lost-update race between two concurrent calls annotating different sections of the same document. - commands/doc_index.py: a non-UTF-8 file now reports a clean ERROR result instead of crashing with a raw traceback; --help now explains that section_level is inferred from heading frequency, not a fixed level. - commands/validate_toc.py: a read failure on one file in a multi-file batch (permission denied, binary content, a TOCTOU race) is now recorded as that file's own ERROR result instead of raising out of the loop and discarding every result already collected for files validated earlier in the same invocation; extracted _validate_one_file() to keep cmd_validate_toc's own local-variable count under pylint's threshold. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: TECK KEAT WILSON <yeow.teck.keat@constructor.tech>
Whichever CLI test runs first in the full suite triggers cli.py's own _configure_studio_logging(), which sets the "studio" logger's propagate to False for the rest of the process -- a real ambient global-state mutation that silently blocks pytest's caplog (listening on the root logger) from seeing any "studio.*" child logger's records for every test that runs afterward. The two new WARNING-level log assertions from the previous commit passed in isolation but failed as part of the full suite for exactly this reason. Added a studio_logger_propagates fixture that temporarily restores propagation for the duration of a test, and used it in both affected tests. Verified against the actual failure condition by forcing propagate=False before running them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: TECK KEAT WILSON <yeow.teck.keat@constructor.tech>
tkcoding
force-pushed
the
jit-retrieval-cascade
branch
from
September 1, 2026 06:57
3493f4b to
dc3c8d3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on constructorfabric#108 (constructorfabric#108)
This branches from
jit-retrieval-doc-index, the branch behind constructorfabric#108 — it needsdoc_index.py/toc.pyto exist first, so it's opened here (within this fork) rather than against upstream directly, since GitHub won't let a cross-repo PR base on a branch that only exists on the fork. Retarget toconstructorfabric/studio:mainonce constructorfabric#108 merges.Summary
Resolves two of constructorfabric#108's follow-up open questions (findings doc §13b/§13c).
infer_section_level(): picks which heading level represents one real retrievable section, using the level's frequency as the signal. A document's real recurring structure (its chapters) shows up as the level used most often; an occasional heading at an anomalous level — exactly what PDF-to-Markdown conversion produces, since it assigns levels by font-size heuristics, not semantic depth — is rare precisely because it's noise, not structure. Levels used only once are excluded as candidates outright.This is a direct, verified fix for a real failure found building feat(toc,doc-index): add JIT-retrieval readiness checks and cached doc index constructorfabric/studio#108: a real PDF-converted document put all 8 of its actual chapters on H5 and a single stray subsection on H3. Treating H3 as "the" section level (or any fixed level) turned the entire back half of the document into one fake 6,601-line "section". Re-run against that same document with this change: 12 correctly-sized real sections, not one.
retrieval_sections(new field on the built index): headings grouped at exactly the inferred level — off-level stray headings stay inside whichever section they geographically fall under, rather than splitting one apart — each with a SHA-256 hash of its own text.diff_stale_sections(): compares a file's current content against its last cached build at this granularity and reports which sections actually changed, matched by position (not heading text — duplicate titles are real, seetoc-heading-duplicate). This is the piece a future caller needs to re-summarize only what changed instead of the whole document — the whole-file etag from feat(toc,doc-index): add JIT-retrieval readiness checks and cached doc index constructorfabric/studio#108 can only say "something changed", not "what".Existing
sections/annotate_section_summary/etag behavior is untouched.Test plan
pytest tests/test_doc_index.py tests/test_toc.py— 156 passed, 100% coverage on touched filespylint/vultureclean;cfs validate0 errors;spec-coveragethresholds metinfer_section_levelre-run for real against the actual PDF-converted document that originally exposed the bug (see commit message)