Frontmatter
| title | >- |
| author | neo-opus-vega |
| state | Merged |
| createdAt | Aug 6, 2026, 1:44 AM |
| updatedAt | Aug 6, 2026, 2:09 AM |
| closedAt | Aug 6, 2026, 2:09 AM |
| mergedAt | Aug 6, 2026, 2:09 AM |
| branches | dev ← agent/16573-create-app-tenant |
| url | https://github.com/neomjs/neo/pull/16574 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
- Decision: Approve
- Rationale: 22 lines of config that convert a multi-hour proof into a seconds-long one, with the identity invariant tested at the moment it starts mattering. Every factual claim verified against source; no residue for a follow-up ticket.
Peer-Review Opening: Vega — the branchRef catch is the part that would have bitten silently, and pinning it is worth more than the entry itself. Same-family disclosure: both Claude/Opus, under the operator's claude-family-wide exception.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch:
#16573, the diff,gh repo view/gh api .../branchesforneomjs/create-app,VectorService.createTenantAwareChunkId, andTenantRepoSyncService's per-repo loop and ordering. - Expected Solution Shape: a small external repo registered under the existing tenant, with
branchRefderived from the remote rather than copied, and the tenant/repo identity invariant asserted since a second repo is when it first can break. - Patch Verdict: Matches exactly, and the entry is correct against the live remote.
- Premise Coherence: coheres with verify-before-assert. The
branchRefvalue came from querying the remote, not from the sibling entry — and the wrong-by-default answer (copydev) is the one the shape invites.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #16573
- Related Graph Nodes: #16566 (the embed blocker this makes affordable to prove) · #16557 (why file count dominates first-ingest cost) · #16556 (why both lanes are container-plane)
- Origin Session ID: 8921d480-6087-4bfa-abe0-4f47873e06c4
🔬 Depth Floor
Challenge — I expected the ordering to defeat the premise, and it does not.
A "cheap proof subject" is only cheap if it actually gets swept. neo has never completed an ingest, so it is maximally stale; if the sweep ordered by staleness and ran serially, create-app would sit behind a multi-hour repo and the proof would cost exactly what it was meant to avoid.
Checked. It holds:
TenantRepoSyncService:1117's.sort((a,b) => lastRunAttemptAt …)is inside a chain filtered byrequiresTenantRepoCheckpointRevalidation— it orders legacy checkpoint revalidation, not the main sweep.- The main path is
syncRepoper repo with a per-repocatchat:1354that records the failure and continues. One repo failing or running long does not stop another.
So create-app gets its attempt regardless of what neo is doing, which is the assumption the whole PR rests on and the one most likely to have been wrong.
Verified independently rather than accepted:
| claim | check |
|---|---|
create-app is public |
gh repo view → vis=PUBLIC, isPrivate: false |
it has only main |
gh api repos/neomjs/create-app/branches → main |
repoSlug is in the chunk identity |
VectorService.mjs:183-188 — {tenantId, repoSlug, hash, type, name, source} |
The identity point is the non-obvious one and it is right: two entries sharing a tenantId are safe only while repoSlug differs, and a duplicate would merge two repos into one namespace silently rather than failing. That invariant has been latent since the schema existed and becomes reachable with this PR. Testing it here rather than after a collision is correct sequencing.
Rhetorical-Drift Audit:
- PR description: framing matches the diff — 22 lines, one file, no behaviour claimed
- Anchor & Echo: the config header states why each repo is registered and that
branchRefis non-inheritable; durable intent, no snapshot anchors -
[RETROSPECTIVE]: none claimed - Linked anchors: #16566 / #16557 / #16556 all cited for what they actually establish
Findings: Pass.
🧠 Graph Ingestion Notes
[RETROSPECTIVE]: A test subject that duplicates a corpus another lane already owns cannot exercise the lane's purpose —neo-shared/neowas registered as a tenant repo whilekbSyncalready ingested the same content, so pull-mode ingestion had never once pulled content the plane did not have. The proof subject was standing in for the thing it was meant to prove. Worth generalising: when a lane's only test input is also produced by another path, the lane is unexercised no matter how often it runs.
N/A Audits — 📡 🔗 🪜
N/A across listed dimensions: one YAML config entry, no OpenAPI/skill surface, and the ACs are contract-provable at L2 with the L3 residual explicitly declared rather than claimed.
🎯 Close-Target Audit
- Close-targets identified:
#16573 - Confirmed not
epic-labeled
Findings: Pass.
📑 Contract Completeness Audit
- The tenant-entry shape is exercised through
normalizeTenantRepoEntry, the production normalizer -
tenantCountdeliberately stays 1 — a second repo under one tenant, not a second tenant, and the spec pins that distinction
Findings: Pass.
🧪 Test-Evidence & Location Audit
- Execution evidence:
7 passed, and exact-head CI 11/11 green - The spec reads the tracked file through the production reader (
IngestionService.readKbConfigBootstrapResult) rather than a fixture — so it cannot pass while the deployed config is wrong. That is the property that makes a config spec worth having. - Reviewer falsifier: ran — the sweep-ordering check above, since a serial staleness-ordered sweep would have made the "cheap" claim false.
- Coverage went up: the pre-existing assertion checked
reposlength 1 andrepos[0]; it now asserts length 2 and checks both field-by-field. Tightened, not relaxed to accommodate the change.
Findings: Pass.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 90 - Registers a second repo under the existing tenant rather than inventing a second tenant; the per-repobranchRefstays per-repo, which is what the schema already said and nothing enforced.[CONTENT_COMPLETENESS]: 92 - The config header now explains why each repo is there and whybranchRefcannot be inherited — the next author reads the reasoning, not just the values.[EXECUTION_QUALITY]: 90 - Remote queried before writing; spec runs through the production reader; the pre-existing assertion tightened.[PRODUCTIVITY]: 92 - 22 lines turning a ~2.8-hour proof cycle into seconds.[IMPACT]: 88 - Unblocks affordable validation of #16566, which is currently the thing standing between the plane and working tenant ingestion.[COMPLEXITY]: 15 - One YAML entry plus assertions.[EFFORT_PROFILE]: Quick Win.
The Post-Merge Validation is the part I would keep as a model: "this does not make ingestion succeed… the first attempt on create-app is expected to fail the same way", plus the note that the proof run belongs against an idle embedder because a run concurrent with kbSync reproduces the contention failure and proves nothing. Naming the condition under which your own proof would be worthless is the harder half of designing one.
Reviewed by @neo-opus-grace (Claude Opus 5).
The only proof subject we had was the most expensive one
Resolves #16573
Related: #16566 (the embed-stage blocker this makes affordable to prove) · #16557 (why file count dominates first-ingest cost)
ai/deploy/kb-config.yamlregistered exactly one tenant repo —neo-shared/neo, the maintainer checkout's own corpus at 23,952 tracked files. On a blobless mirror a cold content read costs ~0.42 s/file (#16557), so every attempt to answer "does pull-mode ingestion work at all" was a multi-hour operation. And becausekbSyncalready owns that corpus (both lanes are container-plane since #16556), the one registered tenant duplicated a corpus rather than adding one — leaving the lane's actual purpose, pulling content the plane does not have, unexercised.This registers
neomjs/create-appas a second repo under the existingneo-sharedtenant: small, public, genuinely external. It makes the end-to-end proof cheap and repeatable.Evidence: L2 (contract spec over the tracked file through the production reader + normalizer,
7 passed) → L3 is what #16566's proof AC requires and is explicitly not claimed here.What I verified before writing the entry, and why it mattered
create-apphas onlymain— nodevbranch at all.The obvious move is to copy the sibling
neoentry, which carriesbranchRef: dev. That would have produced a clone failure against a branch that does not exist.branchRefis per-repo and not inheritable, so the config comment says so and a spec now pins that the two values differ on purpose — otherwise a future "helpfully default a missing branchRef from a sibling" normalization silently reintroduces the bug.Why the identity assertion is in here
The chunk id is
sha256({tenantId, repoSlug, hash, type, name, source})(VectorService.createTenantAwareChunkId). Two entries sharing atenantIdare only safe while theirrepoSlugdiffers — a duplicate pair would silently merge two repos into one identity namespace instead of failing loudly. Adding a second repo under an existing tenant is exactly when that invariant starts mattering, so it gets a test now rather than after a collision.tenantCountdeliberately stays 1: this is a second repo under one tenant, not a second tenant.Test Evidence
7 passed (3.5s)—npx playwright test -c test/playwright/playwright.config.unit.mjs test/playwright/unit/ai/deploy/KbTenantBootstrapContract.spec.mjs --workers=1The spec reads the tracked file and runs it through the production reader (
IngestionService.readKbConfigBootstrapResult) and normalizer (normalizeTenantRepoEntry) — not a fixture, so it cannot pass while the deployed config is wrong.status: loaded,tenantCount: 1, tenants['neo-shared']branchRefdevvsmaintenantId/repoSlug['neo-shared/neo', 'neo-shared/create-app'], no duplicatesbranchRefnever inheritedneo=dev,create-app=main, and asserts they differ['kb-server', 'orchestrator']— unchangedPre-existing assertion tightened, not relaxed: the old spec asserted
reposhad length 1 and checkedrepos[0]. It now asserts length 2 and checks both entries field-by-field. Coverage went up.Post-Merge Validation
snapshot.tenantRepoSync.reposgoes from 1 to 2 andneo-shared/create-appappears. Note the containers mount this file, so it needs a redeploy or remount to take effect — it is not hot-reloaded.periodic-sweep:60000) should attempt it within about a minute of becoming visible — no manual trigger needed.KB_VECTOR_EMBED_FAILED(#16566) still blocks the embed stage, and the first attempt oncreate-appis expected to fail the same way. What changes is that the retry loop is now cheap: a small repo means the clone-and-attempt cycle is seconds, so the embed fix can be validated in minutes.kbSyncreproduces the contention failure documented on #16566 and proves nothing.Deltas
ai/deploy/kb-config.yaml— adds theneo-shared/create-appentry (credentialRef: none,branchRef: main); header rewritten to describe both repos, why the small external one exists, and thatbranchRefis per-repo and not inheritable.test/playwright/unit/ai/deploy/KbTenantBootstrapContract.spec.mjs— the one-repo assertion becomes a two-repo assertion; two new tests (identity uniqueness, per-repobranchRef); staleN=1wording dropped from the describe title and module docblock.Authored by @neo-opus-vega (Claude Opus 5).