G1: fence the ingest→ACL chain at the pipeline seam, mark G1/G2/G9 shipped - #535
Merged
Conversation
🦋 Changeset detectedLatest commit: f065025 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
…9 shipped G1's implementation already landed (315841c and follow-ups): the `Connector` seam, `MockConnector`, the file/web/github connectors, the chunker, incremental indexing. `docs/Planning/Feature Gaps.md` was never updated to say so, so §G1, §G2 and §G9 still read as open gaps while G3 and G8 carry ✅ markers. That is how a second agent gets sent to build what exists. The one real hole underneath the stale doc: the guarantee that a connector's ACL survives ingestion was asserted end to end in exactly one place — `github_connector.rs::custom_group_gates_retrieval_end_to_end`. That is a connector test. Rewrite or delete the GitHub connector and the ingest half of G3 loses its only fence, with `ingestion_contract.rs` still green. `ingested_acls_gate_retrieval_for_every_connector` asserts the same chain at the pipeline seam over a `MockConnector`, so it binds every connector present and future: a doc ingested for `group-eng` is readable by a principal carrying that group and returns nothing for `group-fin` or anonymous, while a doc with no ACL stays org-public. Each negative is paired with the entitled-principal positive control on the same query — "nothing leaked" must not be satisfiable by a pipeline that stored nothing, which is a failure mode this repo has shipped. Red before green: with `DocAcl::for_groups(...).attach_to(document)` reverted in pipeline.rs the new test fails with `group-fin must not read the group-eng doc, got 1 hits` — the exact G3 cross-user leak — while the pre-existing contract test stays green, which is what kept the gap invisible. The doc now records what shipped, plus what the ✅ does not cover: `pull` returns `Vec<RawDocument>` rather than the planned `Stream<Document>`, which materializes a whole source per pull and should be re-shaped before Confluence/Jira rather than after (it is a breaking trait change); format extraction (PDF/DOCX) does not exist ahead of the chunker; and rust.yml has no `schedule:`, so G9's gated `external` tier is a convention nothing ever executes. No production behavior changes — one test, one changeset, one doc.
…ished work #534 landed because §G6 claimed "we only helm lint" after the kind deploy-smoke job had shipped. §5's priority list had the same rot: item 1 still named G3 as the highest-severity next increment, and item 4 pointed at G2/G9 by bare letter. Items 1 and 2 are struck (G3 and G1 shipped). Item 4 now names the specific remainders rather than the letters — G2's chunker is done and its format extraction is not; G9's mock and credential-free tier are done and the nightly that would run the gated external tier does not exist. "G2 is done" and "G2's chunker is done, format extraction isn't" send whoever picks this up to very different places.
brentrager
force-pushed
the
g1-connector-acl-contract
branch
from
August 23, 2026 01:36
6356749 to
3d7f988
Compare
brentrager
added a commit
that referenced
this pull request
Aug 23, 2026
…-tests trap Takes sole ownership of §G2 in the gap doc so #535 and #536 don't conflict on the same section. Folds in #535's correct finding — format extraction (PDF / DOCX / rich HTML → text) still has to land ahead of the chunker before any document-store connector works. Also records why the pre-existing green unit tests were not evidence the chunker was correct: each asserted its rule on the one input shape that never triggers the bug (cap checked only with overlap off, spill only on spaced ASCII, paragraphs only with LF). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
g2chunk's #536 wrote the G2 contract suite and it found four silent defects in the shipped chunker (overlap pushed emitted chunks past max_chars; unspaced CJK/URL text never spilled; CRLF destroyed paragraph structure; chunks straddled markdown sections). My §G2 text called the existing unit tests adequate because they were green and named every rule — which is exactly the reasoning this document exists to stop. Each of those tests asserted its rule on the one input shape that never triggers the bug. §G2 reverts to pristine; #536 owns it. Also corrects my own §G1 bullet. I wrote that an AclKnowledgeStore records and enforces the ACL, which reads as though the fence is opt-in wrapping. Traced it: enforcement is adapter-side in all three backends — Postgres parses DocAcl::ACL_METADATA_KEY at ingest into knowledge_vectors.acl and filters in SQL, DynamoDB into an acl attribute post-filtered at read, and InMemoryStorageAdapter already wraps its own knowledge slice. The live admin indexing path passes state.storage.knowledge() and is fenced by that, not by the caller remembering to wrap. The pipeline writing the metadata is therefore the one link no adapter provides for itself, which is what the contract test guards.
brentrager
added a commit
that referenced
this pull request
Aug 23, 2026
A dropped ACL in the chunker fails open, not closed: the pipeline turns Chunk.acl into the DocAcl the adapters filter on, so a chunk arriving with acl: None is stored org-public and retrievable by anyone, with nothing logged. The existing assertion covered the packed path only; this covers the word-spill, character-spill and heading-break paths added in the previous commit. Mutation-checked — forcing acl: None in the chunk builder fails it. Reported by g1conn while tracing adapter-side ACL enforcement for #535. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
brentrager
added a commit
that referenced
this pull request
Aug 23, 2026
A dropped ACL in the chunker fails open, not closed: the pipeline turns Chunk.acl into the DocAcl the adapters filter on, so a chunk arriving with acl: None is stored org-public and retrievable by anyone, with nothing logged. The existing assertion covered the packed path only; this covers the word-spill, character-spill and heading-break paths added in the previous commit. Mutation-checked — forcing acl: None in the chunk builder fails it. Reported by g1conn while tracing adapter-side ACL enforcement for #535. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
brentrager
added a commit
that referenced
this pull request
Aug 23, 2026
…-tests trap Takes sole ownership of §G2 in the gap doc so #535 and #536 don't conflict on the same section. Folds in #535's correct finding — format extraction (PDF / DOCX / rich HTML → text) still has to land ahead of the chunker before any document-store connector works. Also records why the pre-existing green unit tests were not evidence the chunker was correct: each asserted its rule on the one input shape that never triggers the bug (cap checked only with overlap off, spill only on spaced ASCII, paragraphs only with LF). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
brentrager
added a commit
that referenced
this pull request
Aug 23, 2026
A dropped ACL in the chunker fails open, not closed: the pipeline turns Chunk.acl into the DocAcl the adapters filter on, so a chunk arriving with acl: None is stored org-public and retrievable by anyone, with nothing logged. The existing assertion covered the packed path only; this covers the word-spill, character-spill and heading-break paths added in the previous commit. Mutation-checked — forcing acl: None in the chunk builder fails it. Reported by g1conn while tracing adapter-side ACL enforcement for #535. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
brentrager
added a commit
that referenced
this pull request
Aug 23, 2026
* G2: chunking contract test + four silent chunker defects it found Gap G2 was recorded open, but the Chunker the connectors feed shipped with G1. What was missing was the contract suite — and writing it found the chunker wrong on four counts, each of which fails silently rather than loudly. tests/chunking.rs (16 tests) pins chunk count/indices/ids, overlap as a shared word run, metadata+title+source+acl propagation, oversized spill with no word lost, UTF-8 integrity at the boundary, and degenerate configs. Fixed against it: - max_chars is now a hard cap on the emitted chunk, overlap included. Overlap was prepended on top of an already-full chunk, so a default 500/64 chunker emitted up to 565 chars — past the limit the embedding API truncates at. - Text without spaces now spills. Word-boundary-only splitting returned a Chinese/Japanese/Thai document, a long URL or a minified blob as ONE unbounded chunk. The fallback cuts on character boundaries. - CRLF documents split on paragraphs. "\r\n\r\n" contains no "\n\n", so every Windows-authored document arrived as one giant paragraph. - A markdown heading is a hard boundary, so no chunk straddles two sections. Characters, never bytes: slicing to a byte offset to hit a character cap cuts an em-dash or emoji in half. The UTF-8 guard is built on unspaced mixed-width text because spaced text never reaches the character-split path — the first version, built from words, passed against a deliberately byte-slicing implementation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * G2: fold format-extraction remainder into the gap doc, note the green-tests trap Takes sole ownership of §G2 in the gap doc so #535 and #536 don't conflict on the same section. Folds in #535's correct finding — format extraction (PDF / DOCX / rich HTML → text) still has to land ahead of the chunker before any document-store connector works. Also records why the pre-existing green unit tests were not evidence the chunker was correct: each asserted its rule on the one input shape that never triggers the bug (cap checked only with overlap off, spill only on spaced ASCII, paragraphs only with LF). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * G2: pin Chunk.acl across every split path A dropped ACL in the chunker fails open, not closed: the pipeline turns Chunk.acl into the DocAcl the adapters filter on, so a chunk arriving with acl: None is stored org-public and retrievable by anyone, with nothing logged. The existing assertion covered the packed path only; this covers the word-spill, character-spill and heading-break paths added in the previous commit. Mutation-checked — forcing acl: None in the chunk builder fails it. Reported by g1conn while tracing adapter-side ACL enforcement for #535. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * G2: correct the port-scope claim — .NET has its own diverging chunker My §G2 said the other four languages have no ingestion pipeline to port the fixes into. That is true of Go, TypeScript and Python, which never split text, but false of .NET: dotnet/server/src/Chunker.cs is a second chunker carrying all four defects, plus a fifth unique to it — C# string.Length counts UTF-16 code units where Rust counts chars(), so the two implementations chunk the same document to different boundaries for any astral-plane codepoint. Filed as pearl th-ed78a9 rather than fixed here, to keep this PR to the reference implementation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
The finding
I was sent to build G1 — the
Connectortrait, aMockConnector, and an ingest→chunk→embed→store contract test. All of it already exists on main and has since315841c7: the seam inrust/ingestion/src/connector.rs, the mock, the file / web / github connectors, the chunker (G2), and incremental indexing.docs/Planning/Feature Gaps.mdwas never updated to say so. §G1, §G2 and §G9 still read as open gaps while §G3 and §G8 carry ✅ markers — which is how an agent gets dispatched to rebuild shipped code. Most of this PR is correcting that record.The one real hole underneath the stale doc
The guarantee that a connector's ACL survives ingestion —
RawDocument::acl→ chunk → structuredDocAcl→AclKnowledgeStoreside table →AclReader— was asserted end to end in exactly one place:github_connector.rs::custom_group_gates_retrieval_end_to_end.That is a connector test. Rewrite or delete the GitHub connector and the ingest half of G3 loses its only fence, silently, with
ingestion_contract.rsstill green.ingested_acls_gate_retrieval_for_every_connectorasserts the same chain at the pipeline seam over aMockConnector, so it binds every connector present and future:group-engis readable by a principal carrying that group, and returns nothing forgroup-finor for anonymous;group-findoc;Every negative assertion is paired with the entitled-principal positive control on the same query, plus a run-level
documents_pulled == 3/chunks_stored >= 3. "Nothing leaked" must not be satisfiable by a pipeline that stored nothing.Red before green
Reverted
DocAcl::for_groups(acl.clone()).attach_to(document)inpipeline.rs, in place, and re-ran:That is the exact G3 cross-user leak. Note the pre-existing contract test stayed green through the revert — which is precisely what kept this gap invisible.
Restored; full suite green (53 tests: 41 + 6 + 3 + 2 + 1).
What the ✅ markers deliberately do not cover
Recorded in the doc rather than glossed:
pullreturnsVec<RawDocument>, not the plannedStream<Document>. It materializes a whole source per pull. Fine for file/web/github, wrong for a large Confluence space or Jira project — which is exactly what comes next. Re-shaping it is a breaking trait change, so it belongs before the SaaS connectors, not after..github/workflows/rust.ymlhas noschedule:, so G9's gatedexternaltier (#[ignore]+SMOOTH_AGENT_E2E) is a convention nothing ever executes. A live-API break in the web or GitHub connector is invisible until a user hits it.RawDocument::aclthe wayGithubConnectorConfig::acl_groupsalready does, or ingesting them reopens G3.Verification
cargo test -p smooai-smooth-operator-ingestion— 53 passed, 0 failed, 1 ignored (the network-gated web fetch)cargo clippy -p smooai-smooth-operator-ingestion --all-targets— cleancargo fmt --all -- --check— cleanNo production behavior changes: one test, one changeset, one doc.