Skip to content

Promote Artifact Memory v0.1.3 candidate to main - #124

Merged
joefeser merged 38 commits into
mainfrom
codex/promote-dev-to-main-eb08ece794d6
Sep 10, 2026
Merged

Promote Artifact Memory v0.1.3 candidate to main#124
joefeser merged 38 commits into
mainfrom
codex/promote-dev-to-main-eb08ece794d6

Conversation

@joefeser

@joefeser joefeser commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Closes no issue automatically; this is a branch promotion from dev to main.

Promotion Evidence

  • Repository: joefeser/artifact-memory
  • Source: origin/dev @ eb08ece794d65cc04cb4f2d42ab957307d9e0483
  • Promotion snapshot baseline: codex/promote-dev-to-main-eb08ece794d6 starts at eb08ece794d65cc04cb4f2d42ab957307d9e0483
  • Target: origin/main @ 153f2843acc91ed48f53c6a06f0feb333cc9db8f
  • Commits ahead: 36
  • Commits behind target: 0
  • Merge method: merge_commit

Commits To Promote

Validation / Handoff

  • 630 unit tests passed on exact source SHA eb08ece.
  • Public JSON validation, full conformance suite, and full-history public-safety scan passed.
  • Six-runtime cross-SQLite matrix passed with all invariants holding.
  • PR-loop handoff: agent-control pr-loop --repo joefeser/artifact-memory --pr <PR_NUMBER> --base main --require-codex-review --quiet --json
  • PR body newline check: verify this body contains real Markdown newlines, not escaped newline sequences.

Policy Notes

  • Use a merge commit. Do not squash; promotion should preserve dev evidence and commit history.
  • Apply review fixes only to the source-bound promotion branch; do not patch dev while the main PR is under review.
  • This v2 readback ends at promotion-PR admission; it does not certify post-merge source reconciliation. If review fixes must return to dev, use a separate source-based PR with explicit commit identities and evidence; never infer a range or merge unrelated main-only work.

Post-Merge Reconciliation

  • This v2 handoff records promotion-PR admission only. It does not select reconciliation commits or declare source reconciliation complete.
  • If review fixes must return to dev, open a separate source-based branch/PR that names each selected commit and its review evidence. Do not infer eb08ece794d65cc04cb4f2d42ab957307d9e0483..HEAD, merge unrelated main-only work, or patch/force-push dev during this promotion.

Summary by Sourcery

Promote the v0.1.3 candidate with integrity-gated projections and expanded, receipt-backed search capabilities.

New Features:

  • Add digest-bearing search receipts with raw and literal query modes, optional supersession filtering, and optional BM25 ranking.
  • Add behavioral SQLite/FTS5 integrity gating and cross-runtime validation for generated projections.

Bug Fixes:

  • Prevent tampered or schema-substituted SQLite projections from serving unverifiable search and related-read results.
  • Classify invalid search queries using SQLite error codes and fail closed on unavailable projection runtimes.

Enhancements:

  • Strengthen projection contract validation, snapshot consistency, search semantics, and retrieval evidence.
  • Update release metadata, onboarding references, performance measurements, and v0.1.3 candidate documentation.

CI:

  • Extend conformance execution with projection-integrity, search-receipt, literal-search, supersession-filter, and ranking slices.

Documentation:

  • Document projection integrity requirements, search modes, lifecycle filtering, ranking limitations, runtime boundaries, and v0.1.3 release notes.

Tests:

  • Add synthetic acceptance slices, unit tests, CLI coverage, and a cross-SQLite runtime matrix for projection integrity and search behavior.

Chores:

  • Bump the package and CLI version to 0.1.3 and broaden the agent-control compatibility range.

Close the M1 tamper-evidence gap from the corrected retrieval audit of
153f284: a two-step records_fts then records_fts_content forgery served
inverted-index terms that passed content-row validation with the authentic
source_record_set_digest. Every projection read now requires PRAGMA
integrity_check to return ok inside _read_index, mapping any other result
to a typed projection-unavailable failure.

Security: forged search results can no longer pass validation carrying
the authentic digest; detection relies on SQLite >= 3.44 FTS5 xIntegrity
participation (verified on 3.52.0, cross-SQLite matrix still unverified).
Compatibility: additive and fail-closed; no schema, CLI, receipt, or
output-shape changes; clean projections unaffected at ~0.1 ms per query.

Proves the seam with a synthetic two-step forgery fixture and a checked-in
slice receipt replayed by run_conformance.sh. For issue #105; decision
recorded in docs/decisions/0023.
Process the required-reviewer batch from PR #110 (Codex P1/P2, Qodo
bugs 1-2; Sourcery independently flagged the first):

- Reject runtimes whose PRAGMA integrity_check cannot verify the FTS5
  inverted index (SQLite < 3.44) with a typed projection-unavailable:
  on an incapable runtime ok is absence of evidence, not verification.
- Hold one read transaction across contract validation, integrity
  verification, and the caller query, so a concurrent writer cannot
  commit the two-step forgery between check and use.

Security: closes the silent-bypass and check/use windows on the M0
gate. Compatibility: additive fail-closed; no schema, CLI, or receipt
shape changes; clean projections unaffected. Synthetic regression
fixtures cover both paths; slice receipt and contract/decision wording
updated to match.
Gate projection reads on SQLite integrity verification (M0, #105)
Add search_receipt beside the unchanged search_records surface, per the
corrected retrieval audit's Q1 step gated on the M0 integrity gate: the
receipt (artifact-memory/search-receipt/v1) reports the raw query, matched
record IDs, the projection's source_record_set_digest, and the
integrity-gate outcome, pinning query evidence to the exact canonical
record set as WITS 1151 requires. The receipt is issued inside the same
gated read transaction that serves the query, so a tampered index yields
a typed projection-unavailable failure instead of a vouched receipt. A
search-receipt CLI command prints it human-readable or JSON. Both search
surfaces share one query-failure classifier; behavior is identical.

Security: query results become pinnable to the source record set with
gate evidence attached; receipts cannot vouch for unverifiable indexes.
Compatibility: additive only; search_records, related, provenance, and
all pinned fixtures and receipts keep their shapes. Proven end to end
with a synthetic slice receipt replayed by run_conformance.sh. For issue
#106; decision recorded in docs/decisions/0024.
Implement Q2 of the corrected query-surface epic (audit findings F2 and
F4): search and search-receipt accept --literal, which quotes the query
as one FTS5 string with embedded double quotes doubled, so hyphens,
colons, and bare operators match as content instead of being reinterpreted
as query syntax (raw alpha-beta fails as column-filter syntax on SQLite
3.52.0; literal matches the adjacent phrase only). An empty literal query
returns query-invalid without reaching SQLite. Query failures now classify
on sqlite_errorcode & 0xff rather than SQLite message text: 1 is
query-invalid, anything else is projection-unavailable.

The contract now documents the query-surface boundary: search is lexically
restricted to meaning.summary and labels, is an ungated confirmation
oracle over that restricted meaning, applies no context-pack exclusion
policy, and raw mode remains full FTS5 MATCH syntax by default. Receipts
keep digesting the query exactly as typed in either mode.

Security: removes a silent-reinterpretation footgun for agent-driven
queries and drops the dependency on SQLite error wording. Compatibility:
additive flag and defaulted keyword; no schema, receipt, or output-shape
changes. Proven end to end with a synthetic slice replayed by
run_conformance.sh. For issue #107; decision recorded in
docs/decisions/0025.
Address the Sourcery finding on PR #114 (confirmed locally): the empty
literal check ran inside the gated read, so a missing or invalid index
returned projection-unavailable before the caller-input failure and the
docstring's without-reaching-SQLite claim was wrong. Both search surfaces
now evaluate the match expression before _read_index, so caller-input
classification precedes any index access; regression tests pin the
precedence against a missing index.
… and require an FTS5 records_fts

Process the terminal required-reviewer batch from PR #114 (Codex P1/P2,
Qodo):

- Receipts record query_mode (raw|literal), added to the v1 search-receipt
  schema as an optional field so existing receipts stay valid while every
  new receipt identifies which grammar produced its results.
- Literal mode now requires the query's own case-folded bytes in the
  indexed summary or labels on top of the FTS5 phrase match, so
  punctuation and spelling are significant: literal alpha-beta no longer
  matches adjacent 'alpha beta' text, while matching stays
  case-insensitive and single-term.
- The projection contract requires records_fts to be an FTS5 virtual
  table; a regular table with the expected columns previously passed
  column and integrity checks and then misclassified its code-1 MATCH
  failure as query-invalid. It is now projection-unavailable at contract
  validation, before any MATCH executes.

Validated on this head: 607 tests OK and run_conformance.sh OK including
the regenerated search-literal slice (8 operations). For issue #107;
decisions recorded in docs/decisions/0025.
Address the current-head Codex P2 on PR #114: the FTS5 declaration check
searched the whole CREATE statement for 'using fts5', so a non-FTS5
virtual table could smuggle the marker in a comment (fts4 with
/* using fts5 */ passed the gate and let receipts vouch for unsupported
semantics). The contract now parses the module token immediately
following USING and requires it to be exactly fts5; the regression test
covers both the fts4-with-comment bypass and the plain regular table,
and restores a genuine fts5 table between cases to prove reads recover.
Add literal search mode and error-code classification (Q2, #107)
Implement Q3 of the corrected query-surface epic (audit finding F5):
search and search-receipt accept --exclude-superseded, dropping matches
whose record lifecycle is superseded while the default keeps them as
first-class hits. The filter runs inside the same gated read transaction
as the match, composes with both query grammars, and search receipts
record exclude_superseded beside query_mode and the query digest so every
result-affecting parameter is bound and filtered results are replayable.
This is a read-time lifecycle filter, not revocation; revocation
suppression remains a projection-build input.

Security: supersession becomes expressible from search and receipts
state which mode ran, instead of superseded records being
indistinguishable hits. Compatibility: additive flag, defaulted keyword,
and optional receipt field; default behavior and all pinned fixtures
unchanged. Proven end to end with a synthetic slice replayed by
run_conformance.sh. For issue #108; decision recorded in
docs/decisions/0026.
…hanged

Process the current-head required-reviewer batch from PR #115 (Qodo,
Codex P2):

- Raw-mode exclusion now predicates lifecycle in SQL alongside the FTS
  match, and literal-mode exclusion bounds its lookups by the
  post-filtered candidates, so no filtered search materializes every
  superseded record ID or does work proportional to the projection.
- Default search receipts omit exclude_superseded entirely, restoring
  the exact pre-filter v1 shape for consumers pinned to the earlier
  schema; only filtered receipts carry the field.

Validated on this head: 610 tests OK and run_conformance.sh OK with the
regenerated supersession slice. For issue #108; decisions recorded in
docs/decisions/0026.
… exclusion

Address the Sourcery security-lint thread on PR #115: replace the
placeholder-count f-string IN query with one parameterized primary-key
lifecycle lookup per post-filtered candidate. No search path builds SQL
from string concatenation, and the work stays bounded by matched rows
rather than the projection. Validated: 610 tests OK, run_conformance.sh
OK.
Address the current-head Codex P2 on PR #115: the optional
exclude_superseded property accepted false from external producers even
though omission is the contract's only representation of the default,
and such a receipt would also lose the pre-filter shape pinned v1
consumers expect. The property is now const true; a receipt carrying
exclude_superseded: false fails schema validation. Validated: 610 tests
OK, run_conformance.sh OK.
Add the exclude-superseded search filter (Q3, #108)
Implement Q4, the final step of the corrected query-surface epic (audit
finding F3): search and search-receipt accept --rank, ordering results by
FTS5 bm25 relevance with a deterministic record_id tiebreak; default order
stays record_id alone. Per the second opinion's boundary ruling, ranked
receipts carry a result_order label (bm25, record-id tiebreak, explicitly
non-authoritative and corpus-dependent) as an optional v1 field, while
default receipts omit it and keep the pre-ranking shape. Ranking composes
with both query grammars and supersession exclusion inside the same gated
read.

The checked-in slice proves the boundary deterministically: bm25 inverts
record_id order on a paired corpus, and adding three unrelated records
flips the ranked order, so the corpus-dependence disclosure is evidenced,
not asserted. bm25 cost and flip reachability at vault scale remain
unmeasured open limitations, so the flag is not documented as stable.

Security: no authority is claimed over order; ranking is a findability
aid. Compatibility: additive flag, defaulted keyword, and optional receipt
field; default output unchanged. For issue #109; decision recorded in
docs/decisions/0027.
…ted flip

Process the terminal required-reviewer batch from PR #116 (Codex P2,
Qodo x2):

- Rank with the explicit bm25(records_fts) function instead of the
  mutable rank alias: a persisted FTS5 rank configuration
  (bm25(0.0, 0.0)) can steer ORDER BY rank on an index that still
  passes contract validation and integrity_check, letting a tampered
  index receive a verified receipt for attacker-controlled weighting.
  The explicit function ignores the configuration; a regression test
  tampers a valid index and asserts the true order survives.
- Supersession exclusion now composes in the same SQL statement for
  every mode combination, removing the per-result lifecycle queries
  (n+1) from ranked and literal paths.
- The slice's corpus-growth flip now uses three lexically unrelated
  records (no query terms) with document-frequency asymmetry in the
  paired corpus, so the checked-in evidence substantiates the
  unrelated-growth claim it makes.

Validated on this head: 615 tests OK and run_conformance.sh OK with the
regenerated slice. For issue #109; decisions recorded in
docs/decisions/0027.
Address the current-head Codex P2 on PR #116: the module-token check
accepted a recreated records_fts with the same columns but altered
indexing options (labels UNINDEXED, or a different tokenizer), which
passes table_info, module, and integrity checks while silently steering
bm25 and match semantics under a verified receipt. Contract validation
now compares the normalized sqlite_master declaration against the
canonical declaration extracted from the packaged projection contract
itself, so the check cannot drift from the generator; the regression
covers the labels-UNINDEXED and porter-tokenizer variants beside the
earlier regular-table and fts4-with-comment cases. Validated: 615 tests
OK, run_conformance.sh OK.
Add conditional bm25 ranking behind a flag (Q4, #109)
Close the four open limitations from the corrected query-surface epic
record (issue #117):

- Cross-SQLite matrix (scripts/run_cross_sqlite_matrix.py +
  cross_sqlite_probe.py): invariant-checking runner over local
  interpreters, sqlite3 CLI binaries, and pinned Docker images. Observed
  on 3.34.1 and 3.40.1 the runtime floor fails closed and the forgery is
  never served; on 3.46.1, 3.51.0, and 3.52.0 the forgery is detected
  typed and clean reads succeed; gate-passing runtimes agree on
  projection digests and default and ranked search results.
- bm25 measurements (scripts/measure_ranked_search.py): ranked search is
  at cost parity with unranked (57.4 vs 62.6 ms at 1k; 291.3 vs 289.1 ms
  at 5k — revalidation dominates), and single-record additions caused no
  ranked-order flip in forty bounded trials; recorded in the performance
  baseline per decision 0015.
- M1 deployment exploitability recorded in the threat model: the vector
  requires local write access to the generated projection file, the gate
  converts silent forgery into a typed failure, canonical records are
  unaffected, and v0 exposes no network surface to the projection.
- ADRs 0023 and 0027 updated from unmeasured to measured with evidence
  pointers.

Docker use is optional and read-only; the matrix degrades to local
runtimes without it. For issue #117.
Process the terminal required-reviewer batch from PR #118 (Codex P1/P2
x2, Qodo x6, converging):

- Determinism can no longer pass vacuously: at least two distinct
  gate-passing SQLite versions are required for invariants_hold, and the
  receipt reports gate-passing and fail-closed coverage explicitly.
- Local probe failures are recorded as error entries and fail the matrix
  instead of silently vanishing.
- Tier-B floor checks compare version tuples, not strings.
- Docker execution binds to resolved image digests (recorded per entry),
  not mutable tags.
- The probe adds literal-mode search and a logical-projection-snapshot
  digest to the cross-runtime fingerprint, so the determinism claim
  covers the generated projection, not just the pre-SQLite source digest.
- Ranked measurements bind a generator profile and per-scale corpus
  digest, and flip trials use forty distinct single-record additions
  varied in length and term frequency rather than repeated identical
  text.

Rerun evidence: matrix invariants_hold true across 3.34.1/3.40.1 (fail
closed), 3.46.1/3.52.0 (gate passing, identical snapshot digests), and
3.51.0 CLI; measurements unchanged in substance (cost parity, 0/40
flips) and recorded with corpus digests. For issue #117.
…cile measurements

Address the current-head Codex P2 batch on PR #118:

- A Python or Docker runtime whose library tier is unavailable (import
  failure) now fails the matrix instead of being silently untested; only
  the intentionally tier-A-only CLI entries are exempt, marked
  library_tier_expected false.
- A configured sqlite3 CLI whose version or FTS5 probe fails is recorded
  as an error entry and fails the matrix, matching local and Docker
  handling.
- ADR 0027 now cites the recorded bound-receipt values (56.2/56.0 ms at
  1k; 287.9/288.4 ms at 5k) with the generator profile and corpus-digest
  binding, removing the contradiction with the performance baseline.

Validated: matrix invariants_hold true, 615 tests OK,
run_conformance.sh OK. For issue #117.
Correct the prior commit's overstated validation claim: the preserved
error entries exposed that the Homebrew sqlite3 CLI enables defensive mode
by default and rejected the probe's shadow-table write, so the recorded
run at d3f8a5e actually reported invariants_hold false for that entry —
a real-latency class of failure the pre-hardening script silently
dropped. The probe now disables defensive mode explicitly (it is a CLI
configuration, not an engine capability) and both CLI binaries measure
cleanly: six runtimes recorded, 3.34.1/3.40.1 fail-closed, 3.46.1/3.51.0/
3.52.0 detecting, gate-passing versions agreeing on digests, snapshots,
and default/literal/ranked results. 615 tests OK, run_conformance.sh OK.
For issue #117.
Address the current-head Codex P2 pair on PR #118: the .dbconfig
defensive-off dot-command echoes its state to stdout, which the CLI tier
counted as an integrity row — confirmed on both local CLIs — so a
below-3.44 CLI would have been misclassified as detecting the forgery;
the echo is now filtered before interpretation. A Docker tag whose
immutable digest cannot be resolved is a hard error entry instead of
silently executing the mutable tag with no recorded identity. Matrix
rerecorded: six runtimes, invariants hold, CLI integrity rows now
contain only integrity_check output. For issue #117.
Verify the epic's still-unverified limitations with measured evidence (#117)
@sourcery-ai

sourcery-ai Bot commented Sep 10, 2026

Copy link
Copy Markdown

Reviewer's Guide

Promotes the dev branch’s v0.1.3 candidate to main, combining fail-closed SQLite/FTS5 projection integrity gates with additive digest-bearing search receipts, literal and supersession-aware search, optional non-authoritative BM25 ranking, updated release documentation, cross-runtime evidence, and expanded conformance tests.

Sequence diagram for integrity-gated projection search

sequenceDiagram
    participant Caller
    participant Projection as projection._read_index
    participant SQLite
    participant Search as search_records

    Caller->>Search: search_records(index_path, query)
    Search->>Projection: _read_index(index_path)
    Projection->>SQLite: _runtime_verifies_fts5_integrity()
    SQLite-->>Projection: capability result
    Projection->>SQLite: BEGIN
    Projection->>SQLite: _validate_projection_contract()
    Projection->>SQLite: PRAGMA integrity_check
    alt integrity verified
        Projection-->>Search: verified read transaction
        Search->>SQLite: _matched_record_ids()
        SQLite-->>Search: record IDs
        Search-->>Caller: search results
    else unavailable or tampered projection
        Projection-->>Search: ValidationFailure(projection-unavailable)
        Search-->>Caller: rejected result
    end
Loading

Sequence diagram for digest-bearing search receipts

sequenceDiagram
    participant Caller
    participant CLI as CLI
    participant Receipt as search_receipt
    participant Projection as _read_index
    participant SQLite

    Caller->>CLI: search-receipt index query --json
    CLI->>Receipt: search_receipt(index, query)
    Receipt->>Projection: _read_index(index)
    Projection->>SQLite: _validate_projection_contract()
    Projection->>SQLite: PRAGMA integrity_check
    Projection-->>Receipt: verified snapshot
    Receipt->>SQLite: _matched_record_ids()
    Receipt->>SQLite: SELECT source_record_set_digest
    Receipt-->>CLI: digest-bearing receipt
    CLI-->>Caller: query_digest, record_ids, source_record_set_digest, integrity_gate
Loading

State diagram for SQLite projection availability

stateDiagram-v2
    [*] --> Unchecked
    Unchecked --> CapabilityCheck: _runtime_verifies_fts5_integrity()
    CapabilityCheck --> Unavailable: probe fails
    CapabilityCheck --> ContractCheck: probe passes
    ContractCheck --> Unavailable: schema or declaration mismatch
    ContractCheck --> IntegrityCheck: contract valid
    IntegrityCheck --> Unavailable: integrity_check != ok
    IntegrityCheck --> Verified: integrity verified
    Verified --> Reading: query in same read transaction
    Reading --> Verified: read complete
    Unavailable --> [*]
    Verified --> [*]
Loading

Flow diagram for additive search modes

flowchart LR
    Query[Search query] --> Mode{Query mode}
    Mode -->|raw| Raw[FTS5 MATCH syntax]
    Mode -->|--literal| Literal[Quoted single literal term]
    Raw --> Filter{--exclude-superseded?}
    Literal --> Filter
    Filter -->|yes| Lifecycle[Filter lifecycle != superseded]
    Filter -->|no| Rank{--rank?}
    Lifecycle --> Rank
    Rank -->|yes| BM25["ORDER BY bm25(records_fts), record_id"]
    Rank -->|no| IDs[ORDER BY record_id]
    BM25 --> IDs
    IDs --> Receipt{search-receipt?}
    Receipt -->|yes| Evidence[Bind query digest and source record-set digest]
    Receipt -->|no| Results[Return record IDs]
    Evidence --> Results
Loading

File-Level Changes

Change Details Files
Promotes the v0.1.3 candidate and updates release metadata and adoption documentation.
  • Bumps package and runtime versions from 0.1.2 to 0.1.3.
  • Adds v0.1.3 candidate release notes and updates README, onboarding, quickstart, and versioning references.
  • Widens the supported agent-control version range.
.agent-control/lanes/pr-review-loop.yaml
README.md
artifact_memory/__init__.py
docs/onboarding/repository-adoption.md
docs/release/v0.1.3-release-notes.md
docs/release/versioning-and-launch.md
pyproject.toml
Hardens SQLite projection creation and all read paths with exact contract validation and behavioral FTS5 integrity verification.
  • Validates the generated application object set, declarations, FTS5 definition, columns, indexes, and existing metadata invariants.
  • Adds a cached behavioral probe and fails closed on SQLite/FTS5 runtimes that cannot detect inverted-index forgery.
  • Runs validation, integrity checking, and the query in one read transaction and reports typed projection failures.
  • Converts projection creation SQLite failures into projection-unavailable outcomes.
artifact_memory/projection.py
artifact_memory/projection_integrity_slice.py
artifact_memory/schemas/core/projection-integrity-slice-receipt.v1.schema.json
docs/contracts/v0-filesystem-and-projections.md
docs/decisions/0023-gate-projection-reads-on-sqlite-integrity-check.md
docs/security/v0-threat-model.md
Adds additive, receipt-backed search capabilities for literal matching, lifecycle filtering, and optional BM25 ranking.
  • Adds search-receipt library and CLI surfaces that digest the exact query, bind results to the source record set, and report integrity-gate status without retaining the raw query.
  • Adds literal FTS5 mode with punctuation-aware matching and SQLite error-code-based query classification.
  • Adds opt-in superseded-record exclusion while preserving default search behavior.
  • Adds opt-in explicit bm25 ordering with record-ID tiebreaks and non-authoritative, corpus-dependent receipt metadata.
artifact_memory/cli.py
artifact_memory/projection.py
artifact_memory/schemas/core/search-receipt.v1.schema.json
artifact_memory/schemas/core/search-*.json
artifact_memory/search_literal_slice.py
artifact_memory/search_receipt_slice.py
artifact_memory/search_ranking_slice.py
artifact_memory/search_supersession_slice.py
docs/contracts/v0-filesystem-and-projections.md
docs/decisions/0024-digest-bearing-search-receipts.md
docs/decisions/0025-literal-search-and-error-code-classification.md
docs/decisions/0026-exclude-superseded-search-filter.md
docs/decisions/0027-conditional-bm25-ranking.md
docs/contracts/v0-performance-baseline.md
Adds synthetic conformance evidence, cross-SQLite probes, performance measurements, and broad regression coverage for the promoted behavior.
  • Adds replayable fixtures and scripts for projection integrity, search receipts, literal search, supersession filtering, and ranking.
  • Adds a runtime matrix that distinguishes engine observations from Python-library behavior and asserts fail-closed and cross-runtime invariants.
  • Extends conformance execution and unit/CLI tests for tampering, schema substitutions, snapshot behavior, query modes, filtering, receipts, and ranking.
fixtures/synthetic/projection-integrity/**
fixtures/synthetic/search-literal/**
fixtures/synthetic/search-ranking/**
fixtures/synthetic/search-receipt/**
fixtures/synthetic/search-supersession/**
scripts/cross_sqlite_probe.py
scripts/measure_ranked_search.py
scripts/run_cross_sqlite_matrix.py
scripts/run_*_slice.py
scripts/run_conformance.sh
tests/test_cli.py
tests/test_cross_sqlite_matrix.py
tests/test_projection.py
tests/test_*_slice.py
tests/test_ranked_search_measurement.py

Possibly linked issues

  • #unknown: The promoted commits implement the issue’s requested projection coverage and quickstart capability guidance.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Promote Artifact Memory v0.1.3 retrieval and integrity hardening

🐞 Bug fix ✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Fail closed when SQLite projections or FTS5 integrity capabilities cannot be verified.
• Add digest-bearing receipts, literal search, supersession filtering, and optional BM25 ranking.
• Promote v0.1.3 with schemas, conformance fixtures, runtime matrices, tests, and documentation.
Diagram

sequenceDiagram
    actor User
    participant CLI as Search CLI
    participant API as Projection API
    participant Gate as Integrity Gate
    participant DB as SQLite FTS5
    participant Schema as Receipt Schema
    User->>CLI: Submit query flags
    CLI->>API: Search or receipt
    API->>Gate: Open verified snapshot
    Gate->>DB: Validate contract
    Gate->>DB: Check integrity
    DB-->>Gate: Verified snapshot
    Gate-->>API: Read transaction
    API->>DB: Match and rank
    DB-->>API: Record IDs
    API->>Schema: Validate receipt
    Schema-->>CLI: Digest-bound result
    CLI-->>User: Results or typed failure
Loading
High-Level Assessment

The promoted approach is appropriate: behavioral capability detection is safer than trusting SQLite version strings, a single read transaction closes check/use races, and opt-in flags preserve existing search behavior. Version-only gating, mutating default output, and issuing receipts outside the verified snapshot were considered implicitly by the decisions and correctly rejected.

Files changed (69) +4530 / -69

Enhancement (7) +333 / -2
cli.pyExpose advanced search modes and receipts +20/-2

Expose advanced search modes and receipts

• Adds literal, supersession-exclusion, and BM25 flags to search. Introduces the search-receipt command with matching options and typed failures.

artifact_memory/cli.py

projection-integrity-slice-receipt.v1.schema.jsonDefine projection-integrity evidence schema +67/-0

Define projection-integrity evidence schema

• Defines the operations, projection metadata, tamper sequence, gated outcomes, and limitations required by the integrity slice receipt.

artifact_memory/schemas/core/projection-integrity-slice-receipt.v1.schema.json

search-literal-slice-receipt.v1.schema.jsonDefine literal-search evidence schema +52/-0

Define literal-search evidence schema

• Defines deterministic evidence for punctuation-sensitive literal matching, typed invalid queries, and query-mode receipt binding.

artifact_memory/schemas/core/search-literal-slice-receipt.v1.schema.json

search-ranking-slice-receipt.v1.schema.jsonDefine ranked-search evidence schema +62/-0

Define ranked-search evidence schema

• Defines evidence for BM25 ordering, deterministic ties, corpus-dependent rank changes, and non-authoritative receipt labels.

artifact_memory/schemas/core/search-ranking-slice-receipt.v1.schema.json

search-receipt-slice-receipt.v1.schema.jsonDefine search-receipt slice schema +53/-0

Define search-receipt slice schema

• Defines end-to-end evidence that search receipts bind source and query digests without exposing raw query text.

artifact_memory/schemas/core/search-receipt-slice-receipt.v1.schema.json

search-receipt.v1.schema.jsonDefine digest-bearing search receipts +29/-0

Define digest-bearing search receipts

• Adds the public search receipt contract, including query mode, optional lifecycle filtering, optional ranking metadata, source digest, and verified integrity state.

artifact_memory/schemas/core/search-receipt.v1.schema.json

search-supersession-slice-receipt.v1.schema.jsonDefine supersession-filter evidence schema +50/-0

Define supersession-filter evidence schema

• Defines proof that filtering is opt-in, composes with literal mode, and is recorded in filtered receipts.

artifact_memory/schemas/core/search-supersession-slice-receipt.v1.schema.json

Bug fix (1) +358 / -39
projection.pyHarden projection reads and extend search +358/-39

Harden projection reads and extend search

• Validates exact SQLite object declarations, behaviorally verifies FTS5 integrity support, and holds validation and queries in one transaction. Adds literal queries, lifecycle filtering, BM25 ranking, error-code classification, and digest-bearing search receipts.

artifact_memory/projection.py

Tests (36) +2256 / -9
projection_integrity_slice.pyAdd projection-integrity conformance proof +133/-0

Add projection-integrity conformance proof

• Builds and tampers with a synthetic FTS5 projection, then verifies every public read surface fails closed while the clean control remains readable.

artifact_memory/projection_integrity_slice.py

search_literal_slice.pyAdd literal-search conformance slice +108/-0

Add literal-search conformance slice

• Exercises punctuation and quote handling, typed raw-query failures, empty-query rejection, and receipt mode binding against synthetic records.

artifact_memory/search_literal_slice.py

search_ranking_slice.pyAdd BM25 ranking conformance slice +100/-0

Add BM25 ranking conformance slice

• Demonstrates relevance ordering, deterministic receipt metadata, and ranked-order changes after unrelated corpus growth.

artifact_memory/search_ranking_slice.py

search_receipt_slice.pyAdd search-receipt conformance slice +150/-0

Add search-receipt conformance slice

• Verifies library and CLI receipts, source and query digest binding, raw-query omission, typed query errors, and tamper rejection.

artifact_memory/search_receipt_slice.py

search_supersession_slice.pyAdd supersession-filter conformance slice +96/-0

Add supersession-filter conformance slice

• Verifies default lifecycle behavior, opt-in exclusion, literal-mode composition, relationship digest binding, and receipt parameters.

artifact_memory/search_supersession_slice.py

expected-receipt.jsonPin projection-integrity evidence +75/-0

Pin projection-integrity evidence

• Stores the expected receipt proving clean reads and typed rejection across all tampered projection surfaces.

fixtures/synthetic/projection-integrity/v1/expected-receipt.json

record-0001.jsonAdd primary projection-integrity record +24/-0

Add primary projection-integrity record

• Provides searchable canonical content and a relationship for the inverted-index tampering fixture.

fixtures/synthetic/projection-integrity/v1/records/record-0001.json

record-0002.jsonAdd projection-integrity control record +18/-0

Add projection-integrity control record

• Provides a second canonical record describing fail-closed projection behavior.

fixtures/synthetic/projection-integrity/v1/records/record-0002.json

expected-receipt.jsonRefresh scan conformance receipt digests +8/-8

Refresh scan conformance receipt digests

• Updates case receipt references and the aggregate receipt identifier for the current runtime output.

fixtures/synthetic/scan/v2/expected-receipt.json

expected-receipt.jsonPin literal-search evidence +63/-0

Pin literal-search evidence

• Stores expected outcomes for punctuation-sensitive matching, quote handling, typed failures, and receipt binding.

fixtures/synthetic/search-literal/v1/expected-receipt.json

record-0001.jsonAdd hyphenated literal record +24/-0

Add hyphenated literal record

• Provides an alpha-beta record used to prove punctuation-sensitive literal matching.

fixtures/synthetic/search-literal/v1/records/record-0001.json

record-0002.jsonAdd scattered-token literal record +18/-0

Add scattered-token literal record

• Provides separated query tokens that must not satisfy the literal adjacent phrase.

fixtures/synthetic/search-literal/v1/records/record-0002.json

record-0003.jsonAdd quoted literal record +18/-0

Add quoted literal record

• Provides embedded quote content for validating FTS5 literal escaping.

fixtures/synthetic/search-literal/v1/records/record-0003.json

record-0004.jsonAdd spaced literal control record +18/-0

Add spaced literal control record

• Provides adjacent words without punctuation to distinguish spaced and hyphenated literal queries.

fixtures/synthetic/search-literal/v1/records/record-0004.json

expected-receipt.jsonPin BM25 ranking evidence +57/-0

Pin BM25 ranking evidence

• Stores expected unranked, paired-ranked, and corpus-expanded orders with non-authoritative ranking metadata.

fixtures/synthetic/search-ranking/v1/expected-receipt.json

record-0001.jsonAdd first ranked-search candidate +17/-0

Add first ranked-search candidate

• Provides the first term-frequency profile for deterministic BM25 ordering tests.

fixtures/synthetic/search-ranking/v1/records/record-0001.json

record-0002.jsonAdd second ranked-search candidate +17/-0

Add second ranked-search candidate

• Provides a contrasting term-frequency profile that initially outranks the first candidate.

fixtures/synthetic/search-ranking/v1/records/record-0002.json

record-0003.jsonAdd ranking corpus control record +17/-0

Add ranking corpus control record

• Adds a nonmatching corpus record used to influence BM25 statistics.

fixtures/synthetic/search-ranking/v1/records/record-0003.json

record-0004.jsonAdd first unrelated ranking record +17/-0

Add first unrelated ranking record

• Adds unrelated alpha-only content for the corpus-growth rank-flip proof.

fixtures/synthetic/search-ranking/v1/records/record-0004.json

record-0005.jsonAdd second unrelated ranking record +17/-0

Add second unrelated ranking record

• Adds another unrelated record to alter corpus-wide BM25 statistics.

fixtures/synthetic/search-ranking/v1/records/record-0005.json

record-0006.jsonAdd third unrelated ranking record +17/-0

Add third unrelated ranking record

• Completes the expanded corpus used to demonstrate non-authoritative ranking changes.

fixtures/synthetic/search-ranking/v1/records/record-0006.json

expected-receipt.jsonPin digest-bearing search evidence +51/-0

Pin digest-bearing search evidence

• Stores expected query/source digest, CLI equivalence, privacy, and tamper-rejection outcomes.

fixtures/synthetic/search-receipt/v1/expected-receipt.json

record-0001.jsonAdd matching search-receipt record +24/-0

Add matching search-receipt record

• Provides the canonical searchable record returned by the receipt fixture.

fixtures/synthetic/search-receipt/v1/records/record-0001.json

record-0002.jsonAdd search-receipt control record +18/-0

Add search-receipt control record

• Provides a related nonmatching record for source-set digest coverage.

fixtures/synthetic/search-receipt/v1/records/record-0002.json

expected-receipt.jsonPin supersession-filter evidence +56/-0

Pin supersession-filter evidence

• Stores expected default, filtered, literal-filtered, relationship, and receipt-binding outcomes.

fixtures/synthetic/search-supersession/v1/expected-receipt.json

record-0001.jsonAdd superseding search record +25/-0

Add superseding search record

• Provides an accepted replacement whose relationship binds the exact superseded record revision.

fixtures/synthetic/search-supersession/v1/records/record-0001.json

record-0002.jsonAdd superseded search record +19/-0

Add superseded search record

• Provides the lifecycle-marked predecessor excluded only when filtering is requested.

fixtures/synthetic/search-supersession/v1/records/record-0002.json

test_cli.pyTest new CLI search behavior +186/-0

Test new CLI search behavior

• Covers typed projection creation failures, search receipts, literal queries, supersession exclusion, and BM25 output in JSON and human formats.

tests/test_cli.py

test_cross_sqlite_matrix.pyTest behavioral runtime classification +64/-0

Test behavioral runtime classification

• Verifies capable backports pass by behavior and incapable newer custom builds are accepted only when library reads fail closed.

tests/test_cross_sqlite_matrix.py

test_projection.pyExercise projection hardening and search modes +629/-1

Exercise projection hardening and search modes

• Adds extensive coverage for schema substitutions, FTS5 tampering, runtime capability, snapshot isolation, receipts, literal semantics, lifecycle filtering, and BM25 ranking.

tests/test_projection.py

test_projection_integrity_slice.pyTest projection-integrity receipt replay +30/-0

Test projection-integrity receipt replay

• Regenerates and validates the checked-in receipt, including typed rejection from every gated surface.

tests/test_projection_integrity_slice.py

test_ranked_search_measurement.pyTest ranking measurement scale parsing +27/-0

Test ranking measurement scale parsing

• Ensures measurement scales reject undersized corpora and preserve valid requested counts.

tests/test_ranked_search_measurement.py

test_search_literal_slice.pyTest literal-search receipt replay +27/-0

Test literal-search receipt replay

• Regenerates the literal-search receipt and validates its expected match and schema.

tests/test_search_literal_slice.py

test_search_ranking_slice.pyTest ranked-search receipt replay +29/-0

Test ranked-search receipt replay

• Regenerates ranking evidence and verifies both BM25 reordering and corpus-dependent order changes.

tests/test_search_ranking_slice.py

test_search_receipt_slice.pyTest search-receipt evidence replay +30/-0

Test search-receipt evidence replay

• Regenerates and validates the digest-bearing search receipt and its expected operation outcomes.

tests/test_search_receipt_slice.py

test_search_supersession_slice.pyTest supersession-filter receipt replay +29/-0

Test supersession-filter receipt replay

• Regenerates the filter evidence and verifies default and filtered result cardinalities.

tests/test_search_supersession_slice.py

Documentation (13) +662 / -16
README.mdPoint release references to v0.1.3 +3/-2

Point release references to v0.1.3

• Updates the release-note links to identify v0.1.3 as the current candidate and v0.1.2 as published.

README.md

v0-filesystem-and-projections.mdSpecify hardened projection and search contracts +107/-3

Specify hardened projection and search contracts

• Documents exact schema validation, FTS5 integrity gating, snapshot isolation, search receipts, literal mode, lifecycle filtering, BM25 ranking, and replay commands.

docs/contracts/v0-filesystem-and-projections.md

v0-performance-baseline.mdRecord ranked-search performance evidence +28/-0

Record ranked-search performance evidence

• Adds measured 1,000- and 5,000-record BM25 costs and documents corpus-dependent ranking flips.

docs/contracts/v0-performance-baseline.md

0023-gate-projection-reads-on-sqlite-integrity-check.mdRecord projection integrity-gate decision +94/-0

Record projection integrity-gate decision

• Explains the FTS5 inverted-index forgery, behavioral runtime probe, fail-closed policy, transactional snapshot, compatibility, and evidence.

docs/decisions/0023-gate-projection-reads-on-sqlite-integrity-check.md

0024-digest-bearing-search-receipts.mdRecord search-receipt decision +67/-0

Record search-receipt decision

• Documents the additive receipt surface, digest bindings, raw-query privacy boundary, integrity dependency, and compatibility rationale.

docs/decisions/0024-digest-bearing-search-receipts.md

0025-literal-search-and-error-code-classification.mdRecord literal-search decision +79/-0

Record literal-search decision

• Documents punctuation-preserving literal semantics, SQLite error-code classification, exact FTS5 requirements, and search-surface boundaries.

docs/decisions/0025-literal-search-and-error-code-classification.md

0026-exclude-superseded-search-filter.mdRecord supersession-filter decision +64/-0

Record supersession-filter decision

• Documents the opt-in lifecycle filter, candidate-bounded SQL join, receipt binding, and distinction from revocation.

docs/decisions/0026-exclude-superseded-search-filter.md

0027-conditional-bm25-ranking.mdRecord conditional BM25 decision +77/-0

Record conditional BM25 decision

• Documents opt-in explicit BM25 ranking, deterministic ties, non-authoritative receipt metadata, composition, and measured limitations.

docs/decisions/0027-conditional-bm25-ranking.md

repository-adoption.mdAdvance adoption baseline to v0.1.2 +9/-9

Advance adoption baseline to v0.1.2

• Updates onboarding and integration prompts to use the latest owner-signed release rather than v0.1.1.

docs/onboarding/repository-adoption.md

quickstart.mdDocument SQLite capability verification +36/-1

Document SQLite capability verification

• Explains the fail-closed runtime requirement and provides a temporary projection probe. Updates adoption guidance to the signed v0.1.2 baseline.

docs/quickstart.md

v0.1.3-release-notes.mdAdd v0.1.3 candidate release notes +96/-0

Add v0.1.3 candidate release notes

• Summarizes projection hardening, new search modes and receipts, runtime boundaries, compatibility, limitations, and release verification requirements.

docs/release/v0.1.3-release-notes.md

versioning-and-launch.mdSet documented package version to 0.1.3 +1/-1

Set documented package version to 0.1.3

• Updates the reference CLI and package version in the release policy.

docs/release/versioning-and-launch.md

v0-threat-model.mdModel local projection tampering +1/-0

Model local projection tampering

• Adds generated SQLite projection tampering and its fail-closed integrity controls to the threat model.

docs/security/v0-threat-model.md

Other (12) +921 / -3
pr-review-loop.yamlExpand supported agent-control versions +1/-1

Expand supported agent-control versions

• Allows stable agent-control releases below 0.6.0 instead of limiting compatibility below 0.5.0.

.agent-control/lanes/pr-review-loop.yaml

__init__.pySet runtime version to 0.1.3 +1/-1

Set runtime version to 0.1.3

• Bumps the exported package version from 0.1.2 to 0.1.3.

artifact_memory/init.py

pyproject.tomlBump distribution version to 0.1.3 +1/-1

Bump distribution version to 0.1.3

• Updates package metadata for the v0.1.3 candidate.

pyproject.toml

cross_sqlite_probe.pyAdd standalone SQLite behavior probe +188/-0

Add standalone SQLite behavior probe

• Measures FTS5 forgery detection directly and, when importable, exercises library digests, search modes, and fail-closed behavior.

scripts/cross_sqlite_probe.py

measure_ranked_search.pyAdd ranked-search measurement harness +221/-0

Add ranked-search measurement harness

• Builds bounded deterministic corpora, compares ranked and unranked costs, and measures corpus-growth order flips.

scripts/measure_ranked_search.py

run_conformance.shRun new retrieval conformance slices +5/-0

Run new retrieval conformance slices

• Adds projection-integrity, search-receipt, literal, supersession, and ranking slices to the conformance suite.

scripts/run_conformance.sh

run_cross_sqlite_matrix.pyAdd cross-SQLite integrity matrix +314/-0

Add cross-SQLite integrity matrix

• Runs local Python, standalone CLI, and digest-pinned Docker probes. It enforces behavioral fail-closed and cross-version determinism invariants.

scripts/run_cross_sqlite_matrix.py

run_projection_integrity_slice.pyAdd projection-integrity slice runner +38/-0

Add projection-integrity slice runner

• Runs the synthetic integrity proof and optionally compares its receipt with checked-in evidence.

scripts/run_projection_integrity_slice.py

run_search_literal_slice.pyAdd literal-search slice runner +38/-0

Add literal-search slice runner

• Runs the literal-search proof and optionally verifies exact receipt replay.

scripts/run_search_literal_slice.py

run_search_ranking_slice.pyAdd ranked-search slice runner +38/-0

Add ranked-search slice runner

• Runs the BM25 proof and optionally verifies exact receipt replay.

scripts/run_search_ranking_slice.py

run_search_receipt_slice.pyAdd search-receipt slice runner +38/-0

Add search-receipt slice runner

• Runs the digest-bearing receipt proof and optionally verifies exact checked-in evidence.

scripts/run_search_receipt_slice.py

run_search_supersession_slice.pyAdd supersession-filter slice runner +38/-0

Add supersession-filter slice runner

• Runs the lifecycle-filter proof and optionally verifies exact receipt replay.

scripts/run_search_supersession_slice.py

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 security issues, and 1 other issue

Security issues:

  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)
Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="scripts/run_cross_sqlite_matrix.py" line_range="62-64" />
<code_context>
+                }
+            )
+            continue
+        if report["sqlite_version"] in seen:
+            continue
+        seen.add(report["sqlite_version"])
+        entries.append({"runtime": f"{name} ({path})", **report})
+    return entries
</code_context>
<issue_to_address>
**issue (bug_risk):** The matrix drops every later runtime whose reported SQLite version matches an earlier entry, even when the builds have different FTS5 capabilities, library availability, or result behavior. A capable build encountered first can therefore hide an incapable or divergent same-version build, allowing the matrix to report `invariants_hold` without testing every discovered runtime.

**Triggers:** When multiple Python interpreters, Docker images, or CLI binaries expose the same SQLite version but differ in compile-time features, linked-library behavior, or package availability.

**Suggested fix:** Deduplicate by an immutable runtime/build identity, such as executable path plus library/build fingerprint or Docker image digest, and retain separate entries for distinct runtimes even when their SQLite versions match.
</issue_to_address>

### Comment 2
<location path="scripts/run_cross_sqlite_matrix.py" line_range="50-52" />
<code_context>
            completed = subprocess.run(
                [path, str(PROBE)], capture_output=True, text=True, check=True, timeout=180
            )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 3
<location path="scripts/run_cross_sqlite_matrix.py" line_range="180" />
<code_context>
                subprocess.run([binary, "--version"], capture_output=True, text=True, check=True)
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

### Comment 4
<location path="scripts/run_cross_sqlite_matrix.py" line_range="183-185" />
<code_context>
            completed = subprocess.run(
                [binary, "-batch"], input=TIER_A_SQL, capture_output=True, text=True, check=True
            )
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 4 findings to address first, and the change establishes a new projection-integrity and search-evidence contract, and widens the agent-control version boundary to accept 0.5.x runtimes. If either decision is wrong, reverting stops future use but cannot retract receipts or review outcomes already produced, although the impact is informational and does not mutate canonical records or grant execution authority.

Blocking findings: scripts/run_cross_sqlite_matrix.py:64, scripts/run_cross_sqlite_matrix.py:52, scripts/run_cross_sqlite_matrix.py:180, scripts/run_cross_sqlite_matrix.py:185


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread scripts/run_cross_sqlite_matrix.py Outdated
Comment thread scripts/run_cross_sqlite_matrix.py
Comment thread scripts/run_cross_sqlite_matrix.py Outdated
Comment thread scripts/run_cross_sqlite_matrix.py
@qodo-code-review

qodo-code-review Bot commented Sep 10, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Literal search misses Unicode matches ✓ Resolved 🐞 Bug ≡ Correctness
Description
_matched_record_ids sends the unmodified literal query through FTS5’s narrower default token
normalization before applying Python str.casefold() only to the returned rows. Expanding Unicode
folds such as STRASSE versus Straße or ss versus ß are discarded before the post-filter,
affecting both search_records and search_receipt.
Code

artifact_memory/projection.py[R506-519]

+        expression = '"' + query.replace('"', '""') + '"'
+        folded = query.casefold()
+        if exclude_superseded:
+            match_query = (
+                _LITERAL_RANKED_MATCH_EXCLUDING_SUPERSEDED_QUERY
+                if rank
+                else _LITERAL_MATCH_EXCLUDING_SUPERSEDED_QUERY
+            )
+        else:
+            match_query = _LITERAL_RANKED_MATCH_QUERY if rank else _LITERAL_MATCH_QUERY
+        return [
+            row[0]
+            for row in connection.execute(match_query, (expression,))
+            if folded in row[1].casefold() or folded in row[2].casefold()
Relevance

●●● Strong

Concrete Unicode correctness gap; post-filtering cannot recover rows excluded by FTS5 tokenization.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The cited code constructs and executes an FTS5 phrase from the unmodified query, then applies Python
case folding only to the summaries and labels of candidates returned by FTS5. Because the FTS table
uses the default tokenizer, which does not expand ß to ss, rows satisfying the documented
case-folded literal substring rule can be rejected before the final predicate evaluates them.

artifact_memory/projection.py[505-520]
artifact_memory/schemas/core/index-sqlite.v1.sql[40-44]
docs/contracts/v0-filesystem-and-projections.md[90-96]
artifact_memory/projection.py[506-519]
artifact_memory/projection.py[536-545]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Literal-mode candidate selection uses FTS5’s default token normalization, while final matching uses Python `str.casefold()`. Unicode folds that expand characters, such as `ß` to `ss`, can therefore be excluded by FTS5 before the Python predicate can include them.

## Fix Focus Areas
- artifact_memory/projection.py[505-520]
- artifact_memory/schemas/core/index-sqlite.v1.sql[40-44]
- tests/test_projection.py[605-664]

## Recommended Fix
Use a candidate-selection strategy whose Unicode equivalence matches the final case-fold predicate, either by maintaining a consistently case-folded indexed representation and query or by providing a complete fallback candidate path. Preserve punctuation-sensitive literal matching, ensure FTS filtering cannot exclude valid case-folded substring matches, and add regression coverage for expanding folds such as `STRASSE` matching `Straße` and `ss` matching `ß` across both search surfaces.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Search receipts can omit their mode ✓ Resolved 🐞 Bug ≡ Correctness
Description
search-receipt.v1.schema.json defines query_mode but omits it from the required fields, despite
search_receipt always emitting it as a replay-critical identifier of the grammar used. A consumer
can therefore validate a receipt stripped of that field without knowing whether raw or literal query
semantics produced its record_ids.
Code

artifact_memory/schemas/core/search-receipt.v1.schema.json[R7-11]

+  "required": ["schema_id", "outcome", "query_digest", "record_ids", "source_record_set_digest", "integrity_gate"],
+  "properties": {
+    "schema_id": {"const": "artifact-memory/search-receipt/v1"},
+    "outcome": {"const": "complete"},
+    "query_mode": {"enum": ["raw", "literal"]},
Relevance

●●● Strong

Replay-critical producer fields should be schema-required; accepted precedents favor strict receipt
identity binding.

PR-#103

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The producer always emits query_mode, and the contract identifies it as necessary to replay the
result-affecting search parameters and distinguish the grammar that produced the results. However,
the schema requires the neighboring identity fields while leaving query_mode optional, so
validation does not enforce that promise.

artifact_memory/schemas/core/search-receipt.v1.schema.json[7-12]
artifact_memory/projection.py[559-572]
artifact_memory/projection.py[590-608]
artifact_memory/schemas/core/search-receipt.v1.schema.json[7-18]
docs/contracts/v0-filesystem-and-projections.md[103-106]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The search-receipt schema accepts documents without `query_mode`, although raw and literal searches have different result semantics and the producer always emits the field for replayability. A validated receipt missing it cannot identify which grammar produced its results.

## Fix Focus Areas
- artifact_memory/schemas/core/search-receipt.v1.schema.json[7-18]
- artifact_memory/projection.py[590-608]

## Recommended Fix
Add `query_mode` to the schema's top-level `required` array while preserving its existing `raw`/`literal` enum constraint. Add a negative validation test that removes `query_mode` from an emitted receipt and verifies that schema validation fails.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Failed checks can pass as complete ✓ Resolved 🐞 Bug ≡ Correctness
Description
The new slice-receipt schemas validate the top-level outcome independently from operation
outcomes, allowing complete alongside entries marked failed. A modified or independently
produced synthetic receipt containing a failed conformance check can therefore pass validation as
complete, even though every slice producer derives complete only when all operations are
complete or verified.
Code

artifact_memory/schemas/core/search-literal-slice-receipt.v1.schema.json[11]

+    "outcome": {"type": "string", "enum": ["complete", "failed"]},
Relevance

●●● Strong

Accepted schema-hardening precedent supports rejecting contradictory receipt states and binding
fields to contract semantics.

PR-#103

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
All five newly added slice schemas use independent enums for the top-level and per-operation
outcomes, so they admit the same unconstrained complete/failed combination. Each slice generator
instead derives the top-level outcome from the full operation list and classifies a receipt with a
failed operation as failed, showing that schema validation accepts a contradictory state outside the
contract established by the producers.

artifact_memory/schemas/core/projection-integrity-slice-receipt.v1.schema.json[11-23]
artifact_memory/schemas/core/search-literal-slice-receipt.v1.schema.json[11-23]
artifact_memory/schemas/core/search-ranking-slice-receipt.v1.schema.json[11-23]
artifact_memory/schemas/core/search-receipt-slice-receipt.v1.schema.json[11-23]
artifact_memory/schemas/core/search-supersession-slice-receipt.v1.schema.json[11-23]
artifact_memory/search_literal_slice.py[67-77]
artifact_memory/search_receipt_slice.py[102-117]
artifact_memory/schemas/core/search-literal-slice-receipt.v1.schema.json[11-29]
artifact_memory/schemas/core/projection-integrity-slice-receipt.v1.schema.json[11-29]
artifact_memory/schemas/core/search-ranking-slice-receipt.v1.schema.json[11-29]
artifact_memory/search_literal_slice.py[67-86]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The synthetic slice-receipt schemas accept `outcome: complete` when an entry in `operations` has `outcome: failed`, allowing contradictory conformance evidence to pass schema validation even though the generators only classify receipts as complete when every operation is complete or verified.

## Fix Focus Areas
- artifact_memory/schemas/core/projection-integrity-slice-receipt.v1.schema.json[11-29]
- artifact_memory/schemas/core/search-literal-slice-receipt.v1.schema.json[11-29]
- artifact_memory/schemas/core/search-ranking-slice-receipt.v1.schema.json[11-29]
- artifact_memory/schemas/core/search-receipt-slice-receipt.v1.schema.json[11-29]
- artifact_memory/schemas/core/search-supersession-slice-receipt.v1.schema.json[11-29]

## Recommended Fix
Add supported JSON Schema conditional constraints so `outcome: complete` requires every operation outcome to be `complete` or `verified` and cannot contain a failed operation. If failed receipts are intended to represent operation failures, also require `outcome: failed` receipts to contain at least one failed operation, and add negative validation tests for a complete top-level outcome paired with one failed operation.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Users are misled about literal phrases ✓ Resolved 🐞 Bug ⚙ Maintainability
Description
The --literal contract and decision documentation call the behavior “single-term,” while
_matched_record_ids() quotes the entire input as one FTS5 phrase and retains exact case-folded
substring matches. When a query such as alpha beta appears as adjacent text, the checked-in
conformance slice succeeds, so callers may avoid a supported multiword search or misunderstand why
it matches.
Code

docs/contracts/v0-filesystem-and-projections.md[R90-96]

+Both `search` and `search-receipt` accept `--literal`, which treats the query
+as one literal term: the term is quoted as a single FTS5 string with any
+embedded double quote doubled, and a matched record must also contain the
+query's own case-folded bytes in its indexed summary or labels, so
+punctuation and spelling are significant (`alpha-beta` does not match
+adjacent `alpha beta` text) while matching stays case-insensitive and
+single-term. Without the flag, the raw query is passed to FTS5 unmodified
Relevance

●●● Strong

Documentation conflicts with exercised behavior, and this team accepts corrections to normative
contract language.

PR-#103

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The implementation wraps the entire supplied query in quotes as a single FTS5 expression instead of
rejecting or splitting input containing whitespace, and the literal-search conformance slice
explicitly verifies a successful alpha beta query. These exercised behaviors conflict with the
contract and decision documentation that describe literal mode as accepting one literal term or
being single-term.

artifact_memory/projection.py[505-519]
artifact_memory/search_literal_slice.py[15-19]
artifact_memory/search_literal_slice.py[42-49]
docs/contracts/v0-filesystem-and-projections.md[90-98]
artifact_memory/projection.py[491-519]
artifact_memory/search_literal_slice.py[15-55]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The public projection contract and decision documentation describe `--literal` as a single-term mode, but the implementation accepts multiword input as a quoted adjacent FTS5 phrase and the literal-search conformance slice verifies that behavior.

## Fix Focus Areas
- docs/contracts/v0-filesystem-and-projections.md[90-98]
- docs/decisions/0025-literal-search-and-error-code-classification.md[22-31]
- docs/decisions/0025-literal-search-and-error-code-classification.md[63-66]
- artifact_memory/projection.py[491-519]
- artifact_memory/search_literal_slice.py[15-55]

## Recommended Fix
Replace the single-term wording in the contract and decision documentation with a precise statement that literal mode accepts one literal query string, including a multiword adjacent phrase. Include a multiword example and preserve the documented case-insensitive substring, punctuation-significance, and punctuation post-filter semantics.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. The compatibility matrix can hang indefinitely ✓ Resolved 🐞 Bug ☼ Reliability
Description
_run_cli_binaries() invokes both the SQLite version command and SQL probe without a timeout,
unlike the bounded local and Docker probes. A stuck executable or malformed CLI session at either
configured path blocks the entire compatibility matrix and its calling CI job instead of recording a
failed runtime and continuing with the other runtimes.
Code

scripts/run_cross_sqlite_matrix.py[R179-185]

+            version = (
+                subprocess.run([binary, "--version"], capture_output=True, text=True, check=True)
+                .stdout.split()[0]
+            )
+            completed = subprocess.run(
+                [binary, "-batch"], input=TIER_A_SQL, capture_output=True, text=True, check=True
+            )
Relevance

●●● Strong

Missing subprocess deadlines create a concrete CI hang risk; bounded execution is a straightforward
reliability requirement.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The local probe has a 180-second timeout and the Docker probes also have bounded execution, but both
subprocess.run() calls in _run_cli_binaries() omit a timeout. The enclosing exception handler
cannot run until those calls return; because TimeoutExpired is already a SubprocessError, adding
deadlines would route timeouts through the existing runtime-failure path.

scripts/run_cross_sqlite_matrix.py[50-61]
scripts/run_cross_sqlite_matrix.py[125-149]
scripts/run_cross_sqlite_matrix.py[179-200]
scripts/run_cross_sqlite_matrix.py[49-59]
scripts/run_cross_sqlite_matrix.py[125-146]
scripts/run_cross_sqlite_matrix.py[172-200]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The SQLite CLI version and integrity probe subprocesses have no execution deadline, so a hung configured binary can prevent the cross-SQLite compatibility matrix and its calling CI job from producing a receipt or failure result.

## Fix Focus Areas
- scripts/run_cross_sqlite_matrix.py[179-185]

## Recommended Fix
Pass a bounded `timeout` to both `subprocess.run()` calls in `_run_cli_binaries()`. Preserve `TimeoutExpired` handling through the existing `subprocess.SubprocessError` handler so each timeout is recorded as a failed runtime probe and the matrix continues evaluating other runtimes.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Invalid measurement options crash or misreport ✓ Resolved 🐞 Bug ☼ Reliability
Description
main() accepts unrestricted integers for --repeats and --trials, passing them directly into
measurement loops and copying the trial count unchanged into the receipt. Zero or negative repeats
reaches statistics.median([]) and raises, while a negative trial count skips all probes but still
emits a receipt claiming that negative number of trials.
Code

scripts/measure_ranked_search.py[R201-203]

+    parser.add_argument("--scales", type=_parse_scales, default=[1000, 5000])
+    parser.add_argument("--repeats", type=int, default=5)
+    parser.add_argument("--trials", type=int, default=10)
Relevance

●●● Strong

Nonpositive measurement parameters cause deterministic crashes or invalid metadata; input validation
is an obvious reliability fix.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Both options are parsed as unrestricted integers: _median_ms collects samples over exactly
repeats iterations before computing their median, so a nonpositive value leaves the sample list
empty, while the trial loop uses the unchecked trial count directly as its range bound and later
records it unchanged as flip_trials. Thus negative trials execute no probes but are still reported
as measurement metadata.

scripts/measure_ranked_search.py[122-128]
scripts/measure_ranked_search.py[165-195]
scripts/measure_ranked_search.py[199-206]
scripts/measure_ranked_search.py[163-193]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The ranked-search measurement CLI accepts zero and negative repeat or trial counts. Nonpositive repeats crash during median calculation, while negative trials execute no probes but produce a receipt containing the invalid trial count.

## Fix Focus Areas
- scripts/measure_ranked_search.py[122-128]
- scripts/measure_ranked_search.py[163-194]
- scripts/measure_ranked_search.py[199-206]
- scripts/measure_ranked_search.py[201-203]

## Recommended Fix
Use an argparse integer type helper that rejects values below one for both `--repeats` and `--trials`, or validate both values immediately after parsing. Raise `argparse.ArgumentTypeError` for invalid values so argparse exits with a usage error before creating measurement data, and add command-level tests covering zero and negative values.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 18 rules
✅ Cross-repo context — repo relationships
  Explored: repo: joefeser/what-is-the-spec (sha: 2a288b6a)
  Explored: repo: joefeser/tracemap (sha: 28f610c5)
Review mode: 🧠 Deep: This promotion contains substantial, bug-dense logic across projection integrity, multiple search modes, SQLite compatibility, CLI behavior, schemas, scripts, and tests, with 88 independent hunks and broad release blast radius.

Grey Divider

Tip of the day
💡 Did you know, you can turn these tips off under Display preferences

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread docs/contracts/v0-filesystem-and-projections.md Outdated
Comment thread artifact_memory/projection.py Outdated
Comment thread scripts/run_cross_sqlite_matrix.py
Comment thread scripts/measure_ranked_search.py Outdated
Comment thread artifact_memory/schemas/core/search-receipt.v1.schema.json Outdated
@joefeser

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-10T17:23:50.373738Z 4d05798 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eb08ece794

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread artifact_memory/projection.py Outdated
Comment thread artifact_memory/search_receipt_slice.py Outdated
Comment thread scripts/run_cross_sqlite_matrix.py
@joefeser

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4d057986ec

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/measure_ranked_search.py
@joefeser

Copy link
Copy Markdown
Owner Author

PR review loop owner override:

  • PR: 124
  • Head: 2e9f04d
  • Stop reason: BOT_REVIEW_CEILING_REACHED
  • Reason: Owner accepts Sourcery's three subprocess findings as false positives because executables come from internal allowlists, are resolved locally, use fixed list arguments, and never use a shell or externally controlled command text; owner also accepts stale Codex-review risk because the only post-review delta implements Codex's P2 timing-warmup correction.
  • Residual risk: high
  • Validation evidence: Exact head 2e9f04d: 639 tests pass; contract, conformance, public-safety, seven-runtime SQLite matrix, and macOS/Linux/Windows CI pass; zero unresolved threads and zero actionable findings.
  • Fresh review recommended: no
  • Owner decision: This is a human owner risk decision, not bot approval.

This note accepts stale required-review risk for this exact PR head only. It does not request bot review, resolve findings, or merge the PR.

@joefeser
joefeser merged commit dd8a124 into main Sep 10, 2026
5 of 6 checks passed
@joefeser
joefeser deleted the codex/promote-dev-to-main-eb08ece794d6 branch September 10, 2026 18:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant