Skip to content

release: conexus 7.36.0 - #1515

Merged
Hellblazer merged 85 commits into
mainfrom
release/v7.36.0
Sep 8, 2026
Merged

release: conexus 7.36.0#1515
Hellblazer merged 85 commits into
mainfrom
release/v7.36.0

Conversation

@Hellblazer

Copy link
Copy Markdown
Owner

Release conexus 7.36.0, paired with engine-service-v0.1.108 (additive; deployed and cloud-gated before this tag).

Engine pairing: REQUIRED_ENGINE_VERSION (0,1,108). Engine delta over v0.1.107 is service/src/main only (catalog restore/trash routes, t3 gc alive-set protecting tombstoned documents, opt-in tombstone_protected_count on /manifest/chashes, schema-qualified SET CONSTRAINTS); no Liquibase changesets, no /version fields. v0.1.108 was tagged on fa21c24 after: full Java suite 2659/0/0/7, CANDIDATE SHAKEOUT PASSED, PUBLISHED-CLIENT WRITE GATE PASSED, RDR-194 CC5 gate clear. Post-publish: ACQUIRE GATE PASSED; conexus deploy STEP-6 green (gate-report-20260908T011402Z-v011.json, parity 113/113, recall 12/12, GC leg 9/9); CLOUD CLIENT-PATH GATE PASSED 4/4; deployed-engine-version recorded.

Client: see CHANGELOG.md 7.36.0. Highlights: nx catalog trash / nx catalog restore (nexus-dkymw); t3 gc protects tombstoned documents and reports the tombstone-protected count (nexus-dkymw, nexus-zewg3); nx catalog setup retired from every remedy (nexus-owna8); MinerU pid/stop fixes and PDF auto-mode Docling skip (nexus-5yrob, aabdr, ajfld, 8sb6x, ho9d2, ydqwo, m5ym5); aspects run on the stamped model and refuse unknown collections (nexus-oc98c, 3ygp3, ngpx0); nx dt index page coverage and DEVONthink metadata (nexus-i0cwh); RDR gate loop and rdr-fix surface (nexus-zbdm0, g7zgw, dv7gw, yxo2l); shared engine build lease (nexus-g6xpa); raw-SQL test-tree ratchet 1661 -> 1191 (nexus-cbo4a).

Battery (release worktree, this tree): see the ship record T2 nexus/release-7.36.0-ship-2026-09-07 for the verdict table.

…xus-cbo4a)

Batch 9 item 1, Sam's directive nexus-zrcj7 (2026-09-05): no session or role
search_path reliance, ever. Deletes the remaining 24 per-class
"ALTER ROLE ... SET search_path" literals plus db.changelog-test-role.xml's
own line -- the role-level half batch 9 item 0 deferred. Nothing replaces
them: PgContainerHelper.bootstrapServiceRole and the test-role changelog now
set no search_path at all, since every legitimate query already goes through
schema-qualified jOOQ generated Tables/Routines or a function-pinned SET
search_path in the function definition itself.

RawSqlGateTest.scanSessionSearchPathReliance gains a third branch that fails
loud on the ALTER ROLE shape (exempting only SharedClusterMutationFalsifyTest's
yhmav_poison_schema falsification value, matched on the captured target
schema rather than file name), the old negative fixture becomes a positive
detection proof, and a resource-scan test asserts db.changelog-test-role.xml
can never regain the line.

Kept raw with reason, untouched here: SharedClusterMutationFalsifyTest's
poison-value ALTER ROLE (nexus-tyiht, a deliberate falsification proof that
SharedCluster's reset-at-acquire clears a cluster-wide role GUC).
…per (nexus-cbo4a)

Batch 9 item 1, second half. Folds ~27 files' hand-rolled
"INSERT INTO nexus.service_tokens ..." sites (prepareStatement,
createStatement().execute, and the local wrapper methods several files
built around one) onto PgContainerHelper.seedServiceToken: the existing
4-arg overload for a plain token/tenant/label seed, and a new 7-arg
overload (scope/expiresAt/revokedAt, each omitted from the INSERT --
never set to a literal SQL NULL -- when the argument is null) for sites
that need those columns.

ServiceTokenScopeBackfillTest, ServiceTokenSchemaLiquibaseTest, and
ReferenceOnlyChunkUpsertTest insert LITERAL (fake) hashes to assert on
later, not a real token's sha256, so they convert onto the typed jOOQ
SERVICE_TOKENS DSL directly instead -- seedServiceToken always hashes its
token argument, which does not fit. ServiceIntegrationTest's hand-built
spike schema (its own differently-shaped service_tokens table, documented
since batch 7) is left untouched.

Re-censused against the real gate (RawSqlGateTest.
noRawExecuteSqlRegressionInTestSources's own STALE CEILING failure
output, ground truth from scan() itself, not a standalone estimate):
combined with the search_path deletion in the prior commit, 36 files
changed, -59 sites; TEST_TREE_RAW_SQL_TOTAL_CEILING 1661 -> 1602. Twelve
files reached zero and their TEST_TREE_RAW_SQL_CEILING entries are
removed outright (145 -> 133 files).
…hape asserts (nexus-cbo4a)

Real production finding surfaced by the search_path deletion (Sam's
directive, nexus-zrcj7): CatalogRepository.deferManifestChunkFk ran
"SET CONSTRAINTS fk_catalog_chunks_chunk DEFERRED" with an UNQUALIFIED
constraint name, which PostgreSQL resolves through search_path. With no
role-level search_path left, it failed "constraint ... does not exist" --
exactly the class of reliance the deletion was meant to surface. Full
Java suite caught it via CatalogDeleteCollectionCascadeTest,
CatalogHandlerDeleteTest, ManifestChunkFkTest,
RdrO8dil7GlobalManifestAntiJoinTest, and StagingPromoteOpsIntegrationTest.
Fixed by schema-qualifying the constraint name; RawSqlGateTest's
SANCTIONED_STATEMENTS entry for deferManifestChunkFk is updated to match.

Same suite run also failed PgVectorRepositoryRawSqlPlanShapeTest's two
bare "<=>" EXPLAIN-text assertions: the vector extension's earlier
relocation into schema nexus (search-path-001/002) means the operator no
longer resolves off search_path, so PostgreSQL now renders the qualified
"OPERATOR(nexus.<=>)" form once no search_path names it. Widened both
assertions to accept either form via a regex on the column projection,
without weakening the dim-column check the assertions exist for.

CombinedQueryParityIntegrationTest.
metadataScoped_excludesTombstonedDoc_evenWhenTopRanked's own precondition
("the tombstoned chunk is a top vector match for its own text") also
failed under -Dgroups=integration, but isolated via a revert-and-rerun of
that file alone: the ORIGINAL (pre-deletion) file fails identically, so
this is pre-existing and unrelated -- filed as nexus-5umlb rather than
fixed here.
…ng; retire stale search_path comments (nexus-cbo4a)

Review fold-in for batch 9 item 1 (T2 [24826] items 1-3, [24827] Significant #1).
The scan's ALTER ROLE branch is now case-insensitive, accepts "=" as well as
"TO", and tolerates a quoted target identifier, with a fixture covering the
three variants. CollectionRegistryFkTest, CatalogRepositoryTest and
SharedCluster's javadoc no longer claim that bootstrapServiceRole or
production set a search_path.
active_installs_28d read 2 within an hour of the v0.1.107 deploy: this
box plus a release-battery rehearsal install. NX_NO_TELEMETRY=1 now sits
in every env -i allowlist under tests/e2e, is exported by the five
non-scrubbing sandbox scripts, and is forwarded by -e into the
migration-rehearsal container. tests/test_e2e_no_telemetry_lint.py pins
all three.
…documented cost (nexus-atylb)

Sam's ruling 2026-09-07: the tail-ranking swap a split embedding-model group
shows against the pre-batching fan-out is an accepted cost of batching, to be
revisited later. _desired_candidate_count's docstring records the acceptance
and the live measurement (rdr, 9 collections, Jaccard 0.667, four ids at
ranks 7-10 within 0.005). The parity test derives each corpus's floor from
search_engine's own split decision (0.6 when the group splits, 0.9 otherwise)
and keeps the strict floor in aggregate as a mean over all queries, so one
tail swap passes while a broad drift still fails. Live run 2026-09-07: nine
queries at 1.000, rdr at 0.667, mean 0.967, passed.
…wag)

RDR-143's SessionStart hook drives wheel + data from a plugin update;
this is the reverse, so `nx upgrade` is one verb for all three. After
the ladder it reads installed_plugins.json (the one reader, shared with
health's orchestration-floor check) and, for each conexus/sn plugin
strictly behind the wheel, runs `claude plugin update <id> -s <scope> -y`
with stdin closed and a 45 s budget (two fit inside the RDR-143 action's
120 s). One "Plugin update:" line per plugin, silent in lockstep, never
fails the upgrade; an exit 0 the parser cannot read is reported as not
confirmed. Skipped under --auto, reported under --dry-run.

Isolation: NX_PLUGIN_REGISTRY is the registry seam; conftest parks it so
no unit test, subprocess included, can run the real claude. 24 unit
tests drive a fake claude. tests/e2e/plugin-lockstep-gate.sh drives the
REAL claude CLI in a scrubbed HOME (marketplace at the previous tag,
install, advance, nx upgrade, registry asserted, real registry mtime
unchanged) and then runs the newest plugin's lockstep hook against the
previous published CLI in a bare venv; wired as a release-battery leg.
Review fold: critique [24831], code review [24832].
Sam's 2026-09-07 ruling on nexus-dkymw (RDR-106 Option A regression):
tombstones are the recovery story, not resurrected backup-before-delete
machinery. nexus.document_restore (catalog-003-soft-delete.xml, RDR-156
P1.2) has existed since that changeset with no caller anywhere in the
stack; nexus-xavu7 found three operator-facing sites telling an operator
to "restore the trashed document(s)" with no surface to do it.

- CatalogRepository: restoreDocument(tenant, tumbler) mirrors
  deleteDocument's direct-jOOQ-DSL tombstone writer, guarded by
  DELETED_AT.isNotNull() (idempotent restore); listTrash(tenant, limit,
  offset) is the read-only counterpart.
- CatalogHandler: POST /v1/catalog/restore, GET /v1/catalog/trash.
- TombstoneFilterGateTest: both methods registered as named
  TOMBSTONE_EXEMPT entries (restoreDocument is the second sanctioned
  un-tombstone alongside upsertDocument's ON CONFLICT arm; listTrash
  mirrors agedTombstoneCount's rationale); floor_setDeletedAtSites 3->4.
- CatalogHandlerEnvelopeConformanceGateTest: both routes classified
  (neither for /restore's scalar response, collectionOk for /trash's
  documents/count envelope).
- New CatalogRestoreTrashTest: restore flips a tombstone and the doc
  reappears in list/show; restore of a live/unknown tumbler is a no-op;
  manifest and chunks survive a tombstone+restore round trip; restore
  after purge-trash has reclaimed the row returns 0; trash lists only
  the calling tenant's tombstones and excludes live documents.

Full service suite: 2635 tests, 1 pre-fix failure (the conformance gate
above, fixed), 0 after. Scoped rerun (CatalogRestoreTrashTest,
CatalogHandlerEnvelopeConformanceGateTest, RawSqlGateTest,
TombstoneFilterGateTest): 88/88 green.
Client half of nexus-dkymw, the engine routes' first caller. Closes
nexus-xavu7 (three sites told an operator to "restore the trashed
document(s)" with no surface to do it) by naming the new verbs at every
one of those sites and removing every remaining "manual restore" /
hand-written-SQL claim.

- HttpCatalogClient.restore_document/list_trash: thin wire wrappers over
  POST /v1/catalog/restore / GET /v1/catalog/trash. 404 propagates raw
  (same discipline as purge_trash) for the CLI to turn into a clear
  engine-floor error.
- factory.py: restore_document joins _SERVICE_ONLY_WRITE_OPS (same
  disposition as purge_trash/delete_many/delete_collection -- a
  service-only op with no local-catalog equivalent, so it is
  deliberately NOT added to the shared CATALOG_WRITE_OPS/
  catalog_protocol.py Protocol pair, which requires a matching canonical
  Catalog method for test_catalog_protocol_fidelity.py's shape check).
  list_trash is a read and needs no whitelist entry.
- New src/nexus/commands/catalog_cmds/trash.py: `nx catalog trash`
  (list tombstones) and `nx catalog restore TUMBLER_OR_TITLE` (title
  resolution falls back to the trash listing when the live resolver
  misses, since live reads hide tombstoned rows by design).
- Updated the three nexus-xavu7 guidance sites (collection_rename.py,
  catalog_cmds/collections.py's rename-collection, catalog_cmds/
  doctor.py's --collections-drift) plus the delete verb's own
  docstring/echo, purge_trash's client/CLI docstrings, store_hook.py,
  db/collection_state.py, docs/operations/t3-health.md, and
  docs/cli-reference.md (new "nx catalog trash / restore" section) to
  name `nx catalog restore` instead of a nonexistent manual-restore
  path or hand-written SQL UPDATE.
- tests/catalog/test_http_catalog_client.py: FakeCatalogHandler gained
  /restore and /trash branches (test_fake_catalog_handler_route_census
  requires every real route to be faked or excluded).
- New tests/test_catalog_trash_restore.py: HttpCatalogClient wire
  contract, _ServiceCatalogWriter admission, and CLI coverage (success,
  no-op, title-fallback-to-trash, ambiguous title, unknown, engine-floor
  404) for both verbs.

restore_many was left out (delete_many's positional-vs-membership-set
return shape isn't a trivial mirror, and nothing client-side needs it
yet).

tests/test_catalog_trash_restore.py + tests/test_catalog_purge_trash.py
+ tests/catalog: 429 passed. ruff check on all touched files: clean.
…tle collision

Two review fold-ins for nexus-dkymw (T2 [24833], [24834]), client tree only.

CRITICAL (docs overclaim, T2 [24834]): every site claiming a tombstoned
document's row/manifest/chunks "stay together for the whole purge-trash
window" and that restore "works throughout" was true only for nx catalog
delete / nx catalog purge-trash. Qualified docs/cli-reference.md (both
mentions), the trash.py module/verb docstrings, http_catalog_client.py's
restore_document/purge_trash docstrings, store_hook.py's
store_delete_catalog_cleanup docstring (which was stating the GENERIC
delete_document contract about its OWN tombstone, even though that
tombstone runs after the MCP store_delete tool has already hard-deleted
the T3 chunk in the same call -- restoring the row there can never bring
the content back), and docs/operations/t3-health.md's collections-drift
guidance -- each now names the three verbs whose chunk/row loss nx
catalog restore cannot undo: nx t3 gc (chunk-indexed_at-clocked orphan
sweep, independent of purge-trash's window, alive-set excludes
tombstones immediately -- can reap a just-tombstoned document's chunks
INSIDE the grace window), the MCP store_delete tool (tombstones the row
and hard-deletes the T3 chunk in the SAME call -- no window at all), and
nx collection delete / nx collection prune (purge_collection_cascade --
irreversible, never tombstones). Behavior of those three verbs is
unchanged; this is documentation only.

IMPORTANT (title-collision bug, T2 [24833]): _resolve_restore_target
tried the live resolver first, so a title present both live and in the
trash resolved to the live doc and reported "already live" with no
mention of the restorable tombstoned copy. Rewrote it to check the live
resolver AND the trash listing together: a title matching both is now
refused as ambiguous, naming every tombstoned tumbler and telling the
operator to pass a tumbler; a title matching only the live doc still
no-ops on "already live" but now says explicitly that nothing under that
title is in the trash.

tests/test_catalog_trash_restore.py: two new tests (live+trash collision
refuses ambiguous; live-only match's refined no-op message).

tests/test_catalog_trash_restore.py + tests/catalog: 397 passed. ruff
check on all touched files: clean.
…ngine)

Sam's second 2026-09-07 ruling on nexus-dkymw: chashesForCollection (the
nx t3 gc / indexer-prune alive-set) must keep a tombstoned-but-not-yet-
purged document's chashes alive until nexus.purge_trash physically
reclaims the row, superseding nexus-mqd6t's original DELETED_AT.isNull()
filter for this one read. Without this, t3 gc's own --orphan-window
clock (independent of purge-trash's window) could reap a just-tombstoned
document's chunks inside nx catalog restore's recovery window, so
restore resurrected an empty shell. Read-invisibility of tombstoned
content elsewhere (search results, getManifest) is unchanged.

- CatalogRepository.chashesForCollection: drop the DELETED_AT.isNull()
  filter; document both rulings inline.
- TombstoneFilterGateTest: register chashesForCollection as a named
  TOMBSTONE_EXEMPT entry citing the supersession.
- CatalogEngineDefects70Test: flip mqd6t's BUG 1 test to the new
  contract (tombstone keeps the chash alive; purge_trash removes it).
- CatalogRestoreTrashTest: add a round-trip test asserting the alive-set
  protects the chunk through a full tombstone -> restore cycle.

Scoped: CatalogEngineDefects70Test + CatalogRestoreTrashTest +
TombstoneFilterGateTest + RawSqlGateTest, 144/144 passed.
Full suite: 2636 run / 0 failures / 0 errors / 7 skipped.
…lient)

Client half of the engine's chashesForCollection alive-set fix
(nexus-dkymw, Sam's second 2026-09-07 ruling): update every carve-out
site that told operators nx t3 gc's orphan sweep could reap a
just-tombstoned document's chunks inside nx catalog restore's recovery
window. That is no longer true -- the alive-set now protects a
tombstoned-but-not-yet-purged document's chashes, so only the MCP
store_delete tool and nx collection delete/prune still bypass the
restore window.

- HttpCatalogClient.purge_trash/restore_document docstrings: narrow the
  carve-out from three paths to two.
- commands/catalog_cmds/trash.py: module docstring and both verb
  docstrings updated the same way.
- commands/t3.py: gc_cmd help text gains a TOMBSTONE PROTECTION section;
  the chashes_for_collection call site's comment documents the ruling.
- indexer.py's _prune_deleted_files alive-set-read comment updated.
- indexer_utils.py's non_complete_documents docstring: "every live
  document's manifest" corrected to include still-tombstoned documents.
- docs/cli-reference.md (nx catalog trash/restore section and the
  purge-trash age-semantics paragraph) and docs/operations/t3-health.md
  (collections-drift guidance): same narrowing.
- tests/db/test_i711w_gap_xfails.py and
  tests/test_catalog_manifest_read_api.py: flip the two mqd6t pins to
  assert protection instead of exclusion, with two-ruling docstrings.
  The physical-reclaim half is pinned at the engine layer only, since
  the wire-level purge-trash endpoint refuses older_than_days < 1 and
  neither fixture has a deleted_at-backdating helper.

Scoped: tests/test_t3_gc.py tests/test_catalog_manifest_read_api.py
tests/db/test_i711w_gap_xfails.py tests/test_catalog_trash_restore.py
tests/test_indexer.py -- 198 passed, 14 deselected, 2 xfailed.
ruff check on all touched files: clean (same 30 pre-existing PLC0415
findings as the unmodified tree, zero new).
… (engine)

Review fold-in (T2 [24838]/[24839], both clean of blockers): the
rationale in the prior commit claiming this fix protects the indexer's
orphan-quarantine prune was stale. Traced _prune_deleted_files: its
delete decision runs through the engine SQL function
nexus.gc_quarantine_orphans (catalog-023 / vectors-005), whose anti-join
checks catalog_document_chunks row existence only and never joined
deleted_at; the client-side fallback that once consulted
chashes_for_collection for orphan classification there was retired at
RDR-191 Phase 6 (2026-08-15, nexus-o8dil.33). chashes_for_collection is
used in that path only as an empty-manifest skip guard.

Reworded, no code changes:
- CatalogRepository.chashesForCollection javadoc: states the genuinely
  fixed caller is the nx t3 gc CLI verb (which diffs T3 chunks against
  this exact returned set); the indexer's own prune was already
  tombstone-tolerant by construction and was never at risk.
- TombstoneFilterGateTest's chashesForCollection TOMBSTONE_EXEMPT
  rationale: same correction.

Scoped: TombstoneFilterGateTest + RawSqlGateTest, 72/72 passed
(19+53, 0 failures, 0 errors).
… (client)

Client half of the engine fold-in: the indexer's orphan-quarantine prune
(_prune_deleted_files) was never at risk from nexus-mqd6t's original
exclusion filter and gains nothing from this fix -- its delete decision
runs through the server-side anti-join nexus.gc_quarantine_orphans,
which checks manifest row existence only and never joined deleted_at,
so it was already tombstone-tolerant by construction. The client-side
fallback that once used chashes_for_collection for orphan
classification there was retired at RDR-191 Phase 6 (2026-08-15); today
it is called only as an empty-manifest skip guard.

- indexer.py's _prune_deleted_files comment: reworded to state the
  above instead of implying this prune needed protecting from a
  restore-window reap.
- indexer_utils.py's non_complete_documents docstring: clarified the
  alive-set description is specific to nx t3 gc's own manifest-vs-T3
  diff, distinct from the indexer's server-side-anti-join prune.
- docs/operations/t3-health.md: softened "now respects that same
  window too" to "protected until purge-trash reclaims the row",
  matching the wording used elsewhere in this diff.

ruff check src/nexus/indexer.py src/nexus/indexer_utils.py: clean.
…ionTxn's, not deleteCollectionTxn's; chunks_collection_fk cited from fk-004; quarantine backfill and zero-blank cases added to Phase 1 and the Test Plan; redundant catalog_documents clause dropped; bead, changeset and jargon glossed on first use
… from catalog-001-5, not fk-002; collectionIsEmpty answers, it does not refuse (three sites); Phase 1 item 1 drops the separate physical_collection check; Finalization Gate reads the six passes and the five verified assumptions; three bead ids glossed
…4 in related_rdrs; eight critiques counted; model_version's fate stated (client-supplied, never parsed, untouched by the walk); legacy_grandfathered and catalog_meta glossed at first use
…al Assumption 4 states the two-tenant segment census as measured (nexus 106 two-segment rows, none with chunks; gate-xr789 none), research-13 cited
… 204-research-14: legacy_grandfathered is RDR-101 Phase 6's flag; model_version is the name's v<n> parsed once at registration and retired with the other parses; the third two-segment citation names research-13; assumption 3 names the version segment
… registration sites, enumerated in 204-research-15; the universal 'only' is gone from assumption 3, the walk paragraph and Phase 1 item 4
…n bullet names the five parse sites, the hardcode's current line (index.py:110), and that Phase 1 replaces the model while the name parse goes with Phase 3 (204-research-16)
…match the artifact's text (nexus-g7zgw.3)

Two legs the reference-rot lint lacked, both taken from RDR-204's gate loop:
a changeset-shaped token whose family exists in the Liquibase changelog must
name a file stem, a changeSet id or the bare family; and an attribution of a
backticked identifier to a changeset or an RDR (created, added, defined,
dropped, owns, or the trailing 'RDR-NNN set' form) requires the identifier
to occur in that artifact's own text. Existing misses are allowlisted by
value with reasons; planted tests keep each leg non-vacuous.
…earch-4 for assumption 3 (fix-check [24860] observation)
…ck, round-capped aggregation, fix-commit rule (nexus-g7zgw.1 .2 .4 .5)

The re-gate block of `nx rdr preamble rdr-gate` now prints whenever a gate
record exists, PASSED or BLOCKED (both RDR-204 rounds that introduced new
Criticals followed a PASSED gate, with the sweep structurally off), carries
the gate round derived from the record's prior chain, and prints a Fix check
section naming the exact diff range, the fix commits and the T2 title
`{id}-fix-check-<tip sha>` whenever the RDR changed since the gated commit.

Skills and command: Layer 0 sweeps the critic's per-finding Sites list; the
Fix check layer verifies only the diff (contradiction within the file,
attribution/count/universal without enumeration, cited source lacking the
claim) before Layer 1; Gate Aggregation blocks on any Critical in rounds 1
and 2 and on ship_blockers only from round 3, recording the rest as
residuals the accept skill dispositions; Criterion 6 never yields a finding;
a fix changes the named fact and nothing else, with the research entry
written before the edit (rdr-research § Pre-edit capture).

Design of record: T2 nexus/deep-analysis-rdr-gate-fix-loop-2026-09-07.
The re-gate block flags a gate record whose fix_check sha differs from its
commit (the invariant was prose-only and the live RDR-204 record already
violated it); the accept skill blocks on the same mismatch. The critique
extractor carries the critic's Sites line into the printed block, the
printed Layer 0 text sweeps that list first, and substantive-critic.md's
canonical Issue format gains the Sites line. A failed git log in the fix
check names the failure instead of printing HEAD as the tip sha. The
attribution lint matches identifiers on word boundaries. Skill, command
and preamble wording aligned (round formula, Layer 1 or Layer 3, the
research-entry clause of the enumeration rule, conservative default when
a Verdict omits ship_blockers).
…he T2 critique prefix instead of carrying a count
… one shape for formula_content_detected (nexus-5yrob, nexus-aabdr)

An empty process_command read is a ps timeout as often as a vanished
process, and deleting the pid file on it would orphan a server that is
still ours; the stop verb now signals nothing and says so, and only a
live command that classifies as something else removes the file. The
Docling-path formula_content_detected event carries source="docling" so
both emitters of the event share one field shape.
…ache (nexus-g6xpa)

The service build lease moves from <checkout>/service/.build-lease to
<git common dir>/nexus-build-lease, so every worktree of the repo shares
it; measured 2026-09-07, three worktree agents ran three full engine
suites at once (twelve surefire JVMs) because each had its own lease.
mvnw-leased.sh, build-gate-jar.sh and build-artifacts.sh now wait for a
live holder (build_lease_acquire_wait, NX_BUILD_LEASE_WAIT, default
3600 s; 0 refuses as before). A pid-less lease directory younger than 10 s
counts as held, so a poller can no longer reclaim a lease mid-populate.
tests/db/_service_fixture.py resolves the same root, pinned by a parity
test from both sides.

build-gate-jar.sh caches the stamped jar in the common dir keyed on the
exact service/ working-tree content (throwaway git index, stamp lines
normalized out), the release_version and the mvn args; a hit copies the
jar and reprints its build_ref, a miss builds and publishes by rename. A
fresh worktree with an unchanged tree no longer pays the nine-minute
rebuild. build-gate-jar_test.sh drives the miss, hit, polluted-stamp and
failure-restore paths against a stub mvnw.

bare_mvnw_lint_test.sh also gains build-artifacts.sh as self-guarded; it
acquires the lease at its top and had been red since it landed.

Reviews: T2 nexus/code-review-nexus-g6xpa-lease-and-gate-jar-cache
[24921], nexus/critique-nexus-g6xpa-lease-and-gate-jar-cache [24922].
…does not know instead of reporting full coverage (nexus-ngpx0)

A bare subject name returned zero catalog entries, so zero gaps, so the
clean-coverage message for a collection where 54 of 58 rows had no
aspect record. Zero rows is a refusal that names the physical collection
name as the remedy, never a pass.
…yped jOOQ DSL

nexus-cbo4a batch 12: 39 -> 16 sites. Seed inserts onto HEAD-schema generated
jOOQ tables (catalog_documents/catalog_links/catalog_collections/hook_failures/
document_aspects/aspect_promotion_log/plans/topics/topic_links). databasechangelog
reads (Liquibase's own bookkeeping table, no jOOQ codegen) onto the schema-agnostic
DSL.table(DSL.name(...))/DSL.field(DSL.name(...), Class) form. The jsonb/timestamptz
cast oracles onto typed DSL.cast chains. One DROP VIEW onto DSLContext#dropView.
The five mid-rollback column-probe wrappers (queryOneNullableString/queryOneInt/
queryOneNullableBoolean/assertNullColumn/assertTimestampEquals) onto a shared
fetchColumn helper typed dynamically per call site, since the column's actual
Postgres type varies by rollback depth (TEXT/INTEGER post-rollback, TIMESTAMPTZ/
JSONB/BOOLEAN post-reapply) -- a fixed generated-table Field would be wrong at
half this file's call sites, so the schema-agnostic form is mandatory here too.

Kept raw with reasons documented inline: bootstrapVectorExtensionsForFreshWalk's
9-site DBA bootstrap (CREATE EXTENSION/CREATE SCHEMA AUTHORIZATION/CREATE
FUNCTION, no jOOQ typed form for any shape, same class as
SchemaMigratorIntegrationTest's own kept-raw admin bootstrap), the CREATE ROLE
nexus_diag BYPASSRLS bootstrap, one execute(sql) replaying grants-nexus-diag-1's
own changeset body verbatim, and dbaBootstrap's 5-site admin/svc role bootstrap
against a dedicated container.

Verified: scoped mvnw-leased.sh test -Dtest=SchemaRollbackRoundTripIntegrationTest,
RawSqlGateTest -- 4 + 53 = 57 tests, 0 failures, BUILD SUCCESS.
… DSL

nexus-cbo4a batch 12: 81 -> 50 sites. databasechangelog reads onto the
schema-agnostic DSL.table(DSL.name("databasechangelog"))/DSL.field(DSL.name(...),
Class) form. nexus.memory INSERT/SELECT onto the generated MEMORY table. The
mid-ladder DROP CONSTRAINT/[NO] FORCE ROW LEVEL SECURITY/ADD CONSTRAINT NOT
VALID/VALIDATE CONSTRAINT sites onto PgContainerHelper's typed DDL helpers
against either a generated table or, for chunks_384/chash_index (dropped at
HEAD), DSL.table(DSL.name("nexus", "<table>")). Seed INSERTs onto generated
jOOQ tables (CATALOG_DOCUMENTS/CATALOG_DOCUMENT_CHUNKS/CHUNKS/
CATALOG_COLLECTIONS).

New PgContainerHelper.installTestObjects(Connection), hoisted out of
applyProductSchema: this file's dedicated-container aged-box tests never call
applyProductSchema, so the typed DDL helpers' nexus_test.* backing functions
were missing on those containers. Installed via the MIGRATING role's own
connection rather than su, since whichever Liquibase run creates
databasechangelog first owns it -- installing via su would leave it
superuser-owned and the migrating role's own product-changelog walk would
then hit "permission denied for table databasechangelog".

VALIDATE CONSTRAINT conversions switch their expected exception from
PSQLException to org.jooq.exception.DataAccessException (a jOOQ Routine call
wraps the underlying PSQLException), matching batch 10's own precedent.

Kept raw with reasons documented inline: bootstrapVectorExtensionsForFreshWalk's
9-site DBA bootstrap, 8 occurrences of a 5-site admin/svc role bootstrap
against a dedicated container (bootstrap() plus 7 aged-box tests), and one
chunks seed INSERT whose bare, deliberately unqualified ::vector cast is the
test's own subject (VectorBinding always renders ::nexus.vector, which would
change what that statement proves).

Verified: scoped mvnw-leased.sh test -Dtest=SchemaMigratorIntegrationTest,
RawSqlGateTest -- 16 + 69(incl. overlap) tests, 0 failures, BUILD SUCCESS.
…helper itself (nexus-cbo4a, critique [24929])
…ubstitution, not a pipe into grep -q (nexus-g6xpa)
…(arXiv:2608.20845)

Replicates the paper's compiled-payload experiment in nexus's own consumer
shape: same reader, prompt and retrieval, payload either the retrieved
chunks or the retrieved documents' aspect rows. On 40 questions over
knowledge__dt-papers the aspects payload scored 27.5% and 32.5% at 2k and
8k token budgets against 75.0% and 85.0% for chunks, with paired wins of
21 to 2 and 22 to 1. Document-level paraphrase aspects are not a retrieval
payload; the atomic claims layer stays unbuilt. Record: T3 catalog
1.11.533, T2 nexus/isc-payload-ab-result-2026-09-07.
…wg3)

CatalogRepository.tombstoneProtectedChunkCount(tenant, collection) reuses
strandedChunkCount's own anti-join -- the same predicate nexus.purge_trash's
chunk sweep uses, tenant-wide and collection-blind on the manifest side --
at olderThanDays=0, so hasProtectingManifest collapses to "referenced by a
live document" only. strandedChunkCount gained a collection-scoping
overload; the filter is output-side only, so a chash tombstoned in one
collection but live-referenced from a document in another collection is
still correctly excluded (matching what purge_trash will actually do).

GET /v1/catalog/manifest/chashes gains tombstone_protected_count in its
existing envelope (additive; old clients ignore the unknown key).

Redo of the client-side cut in critique T2 nexus/critique-nexus-zewg3,
which re-implemented a predicate the engine owns and could disagree with
purge_trash on cross-collection chashes.
…n the catalog row, /conexus:devonthink-index (nexus-i0cwh)

`nx dt index` reads each record's properties over the DEVONthink MCP once
per run and, on a fresh PDF index, compares the pages the extractor
produced text for with DEVONthink's pageCount. A gap is a per-record
failure naming the missing pages and a non-zero exit; --allow-page-gap
accepts it; an unreachable MCP or an extraction with no per-page text is
reported as unverified, never as covered. The gap is recorded as
meta.page_gap on the catalog row and cleared on the next full-coverage
run. The same read stamps year (when the PDF carried none),
meta.devonthink_url and meta.devonthink_page_count beside the
x-devonthink-item identity.

The coverage oracle is the extractor's own per-page callback:
PDFExtractor.extract now records pages_with_text in its result metadata,
the streaming pipeline and the batch chunker hand it to index_pdf, and
index_pdf's return_metadata carries page_count and pages_with_text. Chunk
page numbers only mark where chunks start; a 20-page slide deck in seven
chunks read as thirteen missing pages under that oracle, measured live.

Stage 1 of the route is /conexus:devonthink-index, a thin command over
the verb via nx command-context devonthink-index (selector to
invocation, MCP reachability, knowledge subjects, the coverage rule).
The native verb already existed (RDR-099 P2, RDR-139); the bead's copy-
to-durable-dir step is dropped by Sam's ruling: read in place, identity
on the URI.
…s (nexus-zewg3)

The count is computed only when the request carries with_tombstone_protected=1,
so the indexer's per-collection alive-set call on every nx index repo run pays
nothing for a field it never reads (critique T2 nexus/critique-nexus-zewg3-engine-side
Significant 1). Envelope test covers both forms.
…zewg3)

nx t3 gc now reads tombstone_protected_count from the same round trip
that returns the alive-set (HttpCatalogClient.
chashes_for_collection_with_tombstone_protected wraps GET
/manifest/chashes's additive field), rather than re-deriving it
client-side. Both dry-run and --no-dry-run --yes runs print "Protected
by pending tombstones: N chunk(s) (reclaimed by 'nx catalog
purge-trash' once past its --older-than-days window, never by t3 gc)",
naming nx catalog purge-trash as the verb that reclaims that class.
An engine that predates the field reports the line as "unavailable on
this engine" rather than a confident zero; the gc_audit details and
the structured t3_gc_chunks_deleted log event carry the same value
(null when unavailable, never 0).

Redo of the client-side derivation in e19ac828f (not landed): that
version paged GET /v1/catalog/trash tenant-wide and diffed against a
full-collection get_manifests call, which could disagree with what
purge_trash actually reclaims on a cross-collection chash and cost a
second round trip per run. The engine-side count (prior commit) closes
both gaps.
…re-shape test and ledger entry (nexus-zewg3)

chashes_for_collection_with_tombstone_protected passes with_tombstone_protected=1;
chashes_for_collection does not. The method body now has its own test through
the real _get boundary (present -> int, absent -> None, count reconciliation kept).
docs/wire-contract-pending.md gains the [additive] entry for the split pairing.
…ft fixes

CHANGELOG.md: new 7.36.0 section covering nx catalog trash/restore and the
t3 gc tombstone-protection fix (nexus-dkymw, nexus-xavu7), the retirement
of nx catalog setup from every remedy (nexus-owna8), MinerU pid/stop
hardening (nexus-5yrob, nexus-aabdr, nexus-ajfld, nexus-8sb6x, nexus-ho9d2,
nexus-ydqwo), the PDF auto-mode Docling skip (nexus-m5ym5), the
aspects-list --missing refusal (nexus-ngpx0), the index write-model fix
(nexus-ft04v.34), the RDR gate-loop/rdr-fix surface (nexus-zbdm0,
nexus-g7zgw, nexus-dv7gw, nexus-yxo2l), and developer-facing items (the
shared engine build lease/gate-jar cache, nexus-g6xpa; the raw-SQL
test-tree conversions, nexus-cbo4a; the recall-parity acceptance,
nexus-atylb). Pairs with engine-service-v0.1.108 (additive).

conexus/CHANGELOG.md: a 7.36.0 section rewriting PENDING_RELEASE.md's
Awaiting entries as shipped plugin-surface changes (the ledger itself is
untouched; it clears when source.ref actually advances).

docs/cli-reference.md: fixed two drift spots found in the audit against
this window's commits — nx mineru stop's description was silent about the
new process-group signal, pid-recycle refusal, and inconclusive-read
no-op (nexus-5yrob/aabdr/ajfld/8sb6x); nx enrich aspects-list --missing
didn't mention the new refusal for a collection the catalog does not know
(nexus-ngpx0). docs/architecture.md and README.md were checked against
the same commit range and found clean.

tests/fixtures/bead_ids.txt: refreshed via
scripts/refresh_bead_id_manifest.py to pick up nexus-5yrob and
nexus-ngpx0, both cited in the cli-reference.md fix and both real,
already-filed beads the manifest predates.
…ost; both aspect verbs refuse an unknown collection (nexus-oc98c, nexus-3ygp3)

The scholarly-paper-v1 extractor stamped rows claude-haiku-4-5 while its
subprocess ran on the ambient CLI default (claude-fable-5 on the
measuring box, about ten times the cost). The config's model_version is
now passed as --model, and each extraction logs the envelope's
total_cost_usd, models and duration as aspect_extractor_usage, which the
extractor used to discard. Measured 2026-09-07 on haiku with real
papers: $0.13 to $0.31 per paper against the $1.18 dry-run constant.

nx enrich aspects shared the vacuous shape db86de5 closed for
aspects-list --missing: a bare collection name the catalog does not know
printed "No documents to process" at exit 0. Both verbs now raise the
same refusal, and the audit computes the aspect side first so orphan
rows are still reported.

Tests were green on the extractor and enrich modules before the last
two edits; the engine test substrate is refused while the release
battery holds the service build lease, so the final run is pending.
…al case names no cloud token (nexus-ft04v.34)

The mode-declarations census (CI job "pytest (mode-declarations census)")
failed on 8aaf417: four tests referenced a Voyage token without the
cloud_mode fixture. The two cloud cases now use the fixture; the local
case asserts the bge prefix instead of naming the token it must not
send, so no exclusion entry is needed and the exclusion ratchet holds.
A pipeline buffer written before pages_with_text existed now reads as
unverified, never as a 100% gap: the field stays None end to end when the
extraction metadata lacks it. A MinerU batch names every page it covers
(page_numbers) so the collector counts the whole batch, not its first
page. A DEVONthink record with no usable pageCount is tallied as
unverified with its own reason instead of vanishing from the summary.
DEVONthink's record name becomes the catalog title beside url and year,
which the bead's acceptance names. The failure message names the oracle
it uses.
…d pages_with_text (nexus-i0cwh)

The fake pipeline reports no extraction stats, so pages_with_text is None
(unverified), and the empty-meta shape carries page_count 0 and an empty
pages_with_text list.
… on haiku, usage logged before the envelope check, extraction refusal counts orphans (nexus-oc98c, nexus-3ygp3)

Review of cc4f234 found two operator-facing strings still claiming the
ambient default model as the cost basis and the $1.18 constant still
measured on the wrong model. The constant is now the mean of three haiku
dispatches through the production prompt over real papers ($0.20), the
banner names the configured model, usage is logged before the result-key
check so a malformed envelope's spend is still recorded, and the
extraction verb's refusal names orphan aspect rows the way the audit does.
…al-mode collection token (nexus-i0cwh)

The mode-declarations census reads a cloud embedder token as a cloud_mode
claim; the fake collection list now carries the bge token, so no exclusion
entry is needed.
…th_text None (nexus-i0cwh)

The fake pipeline reports no extraction stats; the dict index_pdf builds
on the streaming path carries pages_with_text None (unverified), not an
empty list.
…oned document's chunk survives until purge-trash and the verb names it (nexus-dkymw)

tests/db/test_i711w_gap_xfails.py's integration-only gc journey still asserted the
pre-ruling contract (the tombstoned doc's chunk is an orphan and gets collected);
the alive-set change (545ed06) flipped the unit-level pins but this test runs
only under the local-service gate, where it went red on the 7.36.0 battery. It now
asserts the chunk survives and the report line counts it as tombstone-protected.
@Hellblazer
Hellblazer merged commit 96d0636 into main Sep 8, 2026
25 checks passed
@Hellblazer
Hellblazer deleted the release/v7.36.0 branch September 8, 2026 02:47
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