Verifier report — cocoindex-ledger-api TECH.md (S242 WP1.2)
Verifier report — cocoindex-ledger-api TECH.md (S242 WP1.2)
Section titled “Verifier report — cocoindex-ledger-api TECH.md (S242 WP1.2)”Date: 18/05/2026 (S242)
Drafter commit: d9790760 (agent worktree agent-a916593d21171c156, branch worktree-agent-a916593d21171c156)
Spec under review: /Users/liamj/Documents/development/knowledge-hub/.claude/worktrees/agent-a916593d21171c156/docs/specs/id-36-cocoindex-ledger-api/TECH.md (381 lines)
Verifier head: 70336a3c (post-reset to origin/content-items-investigation)
Verdict: FAIL — one blocking finding plus three non-blocking findings; rework required before merge to main.
§1 — Summary
Section titled “§1 — Summary”The spec covers the brief well at a structural level: it includes the boundary statement, four functions with signatures, the TS landing-path table, three terminal states with the partial-completed motivator, the LMDB exposure boundary, file:line landing points, a test plan, failure-mode + error-envelope sections, and three gap flags in the construction-guide §5.3 format. UK English clean. No inline TBD / TODO. Feature-spec pattern (Summary+Behavior via /write-tech-spec) — not the construction-guide P-N pattern — correctly applied per S241 critical-rule 5.
However, the spec carries one factual error about a load-bearing precedent (Result<T, E> shape from @/lib/supabase/safe) that would propagate into implementation as a bug, plus two file:line citation slips. These warrant FAIL rather than PASS-WITH-NOTES because the Result<T, E> error is the central pattern the brief specifically called out (“Result envelope per tryQuery() precedent”), and the spec’s example code in §2.3 shows the wrong shape — an implementer reading this spec at T1.3 build-time would copy the wrong discriminant.
§2 — Findings by axis
Section titled “§2 — Findings by axis”§2.1 BLOCKING (1)
Section titled “§2.1 BLOCKING (1)”B1 — Result<T, E> shape is factually wrong (§2.2.1 + §2.3 + §2.2.3 + §2.2.4). The spec describes the Result<T, E> envelope as { data: T, error: null } | { data: null, error: E } with callers branching on error (spec line 141 + example code at lines 192-200). The actual Result<T, E> from @/lib/supabase/safe.ts:108-110 is:
export type Result<T, E = SupabaseError> = | { ok: true; data: T } | { ok: false; error: E };The discriminant is ok (boolean), not error. The actual tryQuery() precedent (verified at lib/supabase/safe.ts:160-181) returns { ok: false, error: ... } on failure and { ok: true, data: ... } on success. The spec’s own §2.2.3 description (“Returns data: null when status is completed / failed / in_progress”) is also incompatible with the actual shape — under the real Result<T, E>, returning a null data success is { ok: true, data: null }, not data: null at the top level. The isOk() type guard at lib/supabase/safe.ts:190-194 reinforces the ok discriminant.
This finding is blocking because:
- The spec explicitly cites
tryQuery()as the precedent (§2.3 line 203). The implementer at T1.3 build-time will pattern-match on the spec’s example, not the actualsafe.tssource, and will produce an inconsistentResult<T, E>shape that breaks the codebase convention. - CLAUDE.md “Silent failures in Supabase calls” gotcha calls out
{ ok, data }/{ ok, error }as the discipline. The spec inverts the discipline by branching onerror. - The spec’s
data: nulloverload forgetPartialResolveDetail(§2.2.3) andcorrelateToPipelineRun(§2.2.4) reads as “the error field is absent” — under the realResult<T, E>, that semantic is encoded as{ ok: true, data: null }, which is a different and stricter type contract.
Fix: rewrite §2.2.1, §2.2.3, §2.2.4, §2.3 example to use { ok: true, data: T } | { ok: false, error: E } shape; update the example handler at lines 192-200 to branch on if (!result.ok); reconcile the data: null overloads explicitly as { ok: true, data: null }.
§2.2 NON-BLOCKING (3)
Section titled “§2.2 NON-BLOCKING (3)”N1 — lib/pipeline/start-run.ts:1-60 citation is wrong (spec §1.5 line 58). The spec cites lib/pipeline/start-run.ts:1-60 as the landing site for startPipelineRun(). Verified: start-run.ts is 180 lines; the function startPipelineRun actually starts at line 100 and ends at line 179. Lines 1-60 are the file-header comment block plus the start of the StartPipelineRunParams interface — the function is not in that range.
Fix: change citation to lib/pipeline/start-run.ts:100-179 (function body), or lib/pipeline/start-run.ts:78-179 (JSDoc + function body).
N2 — §1.4 “schema-volatility insulation” framing is extrapolated, not literally cited (spec §1.4 ref to phase-b-prerequisite-2-cocoindex-deep-dive.md §3.4). The spec attributes “schema-volatility insulation” to §3.4 of the cocoindex deep-dive. Verified content of §3.4 (deep-dive lines 147-151): the section names the gap (“cocoindex’s per-flow-run ledger query semantics + retention aren’t documented at the TS-facing API level”) and recommends keeping pipeline_runs as the KH-facing surface; it does not explicitly use the phrase or framing “schema-volatility insulation”. The drafter is plausibly extending the framing — but the cite reads as if the source explicitly states it.
Fix: either soften the cite (“paraphrased from §3.4”) or expand the inline quote so the extrapolation is visible.
N3 — 00-synthesis-v2.md cite path is implicit, not explicit (multiple references). The spec cites 00-synthesis-v2.md §5.2 row 5 and 00-synthesis-v2.md §5.1 without specifying the subdirectory docs/plans/phase-0-investigation/10-feedback-investigation-findings/. The file lives only in that subdirectory; the top-level phase-0-investigation/ has no 00-synthesis-v2.md. A future agent grepping for the path will resolve to the wrong location (or to none).
Fix: prefix every 00-synthesis-v2.md citation with the path 10-feedback-investigation-findings/00-synthesis-v2.md (matches the actual file location).
§2.3 SUGGESTIONS (3)
Section titled “§2.3 SUGGESTIONS (3)”S1 — §3.4 msw (Mock Service Worker) introduces a new test dependency without a CLAUDE.md or package.json check. The spec proposes msw as the sidecar mock harness without verifying that msw is already a project dependency. If msw is not currently in package.json, the spec implicitly creates a new dependency-add task that is not in scope and is not reflected in the test plan. Alternative: stub the ledger-client module directly via vi.mock() (already named at §3.1) without msw.
Fix: verify msw presence in package.json before T1.3 build, or remove msw from the test design and rely on the vi.mock('@/lib/pipeline/ledger-client', ...) pattern already described at §3.1.
S2 — §2.7 “implementation will overload as needed” is a buried TBD. The spec says: “the public function signatures default to ‘scope-from-auth’ mode … for service-role callers, an overload accepts an explicit workspace_id parameter. … Document in JSDoc. Add a Vitest test that calling the API without authorised context AND without explicit workspace_id returns rls_denied.” The “overload as needed” framing is acceptable but soft — it would benefit from being either (a) explicit in the §2.2 signatures (e.g. getFlowRunMetrics(flow_run_id: string, options?: { workspace_id?: string }): Promise<Result<...>>) or (b) explicitly flagged as a follow-up subtask at T1.3 build-time. As written, the overload requirement might be missed in implementation.
Fix: add the optional options.workspace_id to the signatures in §2.2, or explicitly call it out as the second-half of §2.7.
S3 — §4.5 Bun fetch + HTTP 204 mitigation is well-intentioned but the contract claim deserves verification. The spec asserts: “The cocoindex sidecar HTTP transport should not return 204 — it returns 200 with a JSON body”. This is an assumption about the sidecar contract that is itself flagged as STILL-OPEN in [GAP-LEDGER-003]. If the sidecar HTTP contract turns out to be 204-on-empty-stages (as some REST sidecar shapes do), the §4.5 mitigation reverses into a real implementation issue. The fix is fine but should explicitly link back to [GAP-LEDGER-003] resolution.
Fix: add an inline note in §4.5: “Contract verification depends on [GAP-LEDGER-003] spike.”
§3 — Axis-by-axis verdict
Section titled “§3 — Axis-by-axis verdict”| # | Axis | Verdict | Notes |
|---|---|---|---|
| 1 | Brief compliance | PASS | All seven acceptance criteria from the drafter brief are addressed: boundary statement (§1.2), TS API surface with signatures (§2.2), partial-resolution framing per 02-data-flow.md §7.2 (§1.3), LMDB exposure boundary (§1.4), file:line landing points (§1.5 + §2.1), test plan (§3), failure modes + error envelope (§2.3 + §4). |
| 2 | No-fabrication discipline (citation spot-check) | PASS-WITH-NOTES | Spot-checked ≥5: record-run.ts:128-212 ✓ (function exact); record-run.ts:34-37 ✓ (JSDoc for completed_with_errors); record-run.ts:38-41 ✓ (PipelineRunStatus type); database.types.ts:1768-1842 ✓ (pipeline_runs schema, closing brace at 1842); 02-data-flow.md §7.2 ✓ (text supports N6 RESOLVED rollup framing); cocoindex-deep-dive.md §3.4 ✓ ledger gap; §3.5 ✓ LMDB-per-container. Failures: start-run.ts:1-60 ✗ (function actually at 100-179, see N1); cocoindex-deep-dive.md §3.4 as “schema-volatility insulation” ✗ (paraphrase, not literal — see N2); 00-synthesis-v2.md path implicit ✗ (see N3). |
| 3 | Boundary correctness (pipeline_runs rollup vs LMDB) | PASS | §1.2 boundary table + §1.4 LMDB-exposure prohibition + §2.5 explicit non-modification of recordPipelineRun() / startPipelineRun() collectively make the boundary tight. Per-instance LMDB multi-container framing addressed in §4.2 with DEFERRED-v1.1 disposition. |
| 4 | Result envelope pattern (tryQuery() consistency) | FAIL | See B1. The discriminant is error in the spec but ok in the actual codebase. |
| 5 | Gap-flag format (§5.3) | PASS | Three gap flags all include Category + Sources checked + Prerequisite work. None is a disguised TBD. |
| 6 | Tag vocabulary ([RATIFIED-...], [DEFERRED-v1.1]) | PASS | [DRAFT — Liam review pending] status correct; DEFERRED-v1.1 used for MCP wrappers (§2.9) and gRPC transport (§5.1.2) and multi-instance coordinator (§5.1.3). No inline TBD / XXX / TODO. |
| 7 | Feature-spec pattern (Summary+Behavior via /write-tech-spec) | PASS | Spec uses Context + Proposed changes + Testing + Risks + Follow-ups — the /write-tech-spec skill structure. Not construction-guide P-N pattern, per S241 critical-rule 5. |
| 8 | CLAUDE.md gotcha application | PASS-WITH-NOTES | tryQuery() cited (§1.5 line 62 + §2.3) ✓; “No barrel re-exports” cited (§2.1 line 79) ✓; getAuthorisedClient() discriminated union cited (§2.7) ✓; Bun fetch + HTTP 204 cited (§4.5) ✓. But: the spec’s described Result<T, E> shape contradicts the gotcha (see B1). |
| 9 | Partial-resolution invariants (three terminal states) | PASS | completed / partial_completed / failed distinguished correctly in §1.3 + §2.2.1. The “binary conversion produced empty content_text → LLM extraction skipped” motivator is consistent with 02-data-flow.md §7.2 + the cocoindex 6-stage topology at §3.1. |
| 10 | Regression-fixture lock-in (partial_completed contract) | PASS | §3.3 specifies __tests__/lib/pipeline/fixtures/partial-resolve-malformed-pdf.json with explicit invariant assertions (six stages, short_circuit_reason === 'no_upstream_output', exact skipped_stages array). Lock-in surface is concrete. |
| 11 | UK English | PASS | Searched for behavior, color, organization, serialization, optimization, capitalize — zero matches. |
| 12 | Path-correction discipline | PASS | Spec lives only in worktree (/Users/liamj/Documents/development/knowledge-hub/.claude/worktrees/agent-a916593d21171c156/docs/specs/id-36-cocoindex-ledger-api/TECH.md). Main repo docs/specs/ has no cocoindex-ledger-api/ directory. No leak detected. |
§4 — Verdict
Section titled “§4 — Verdict”FAIL — rework required before merge. One blocking finding (B1 — Result<T, E> discriminant inversion); three non-blocking findings (N1 file:line off, N2 framing extrapolation, N3 implicit path); three suggestions.
Blocking count: 1 Non-blocking count: 3 Suggestion count: 3
Path to PASS-WITH-NOTES: rewrite §2.2.1 / §2.2.3 / §2.2.4 / §2.3 to use { ok: true, data: T } | { ok: false, error: E } shape; fix the example handler to branch on if (!result.ok); fix N1 + N2 + N3. Suggestions (S1, S2, S3) are nice-to-have but do not block PASS.
Path to FAIL → PASS straight-through: apply the B1 + N1 + N2 + N3 fixes in a follow-up commit on the same worktree branch; verifier re-runs (this same report applies to the corrected spec by axis count).
§5 — Verifier process notes
Section titled “§5 — Verifier process notes”- Spec was read in full (381 lines) from the drafter worktree path.
- Citations spot-checked: 8 distinct file:line claims verified, 1 confirmed wrong (N1), 1 paraphrase noted (N2), 1 path-prefix issue noted (N3).
- The
Result<T, E>shape claim was verified by readinglib/supabase/safe.ts:104-194end-to-end. - The drafter brief acceptance criteria were tracked one-for-one in §3 row 1.
- Phase 0 investigation docs referenced for
02-data-flow.md§7.2 N6 RESOLVED +phase-b-prerequisite-2-cocoindex-deep-dive.md§3.3 / §3.4 / §3.5; all support the spec’s framing. - Verifier worktree HEAD verified at
70336a3c(S242 continuation prompt). Worktree status clean prior to verifier-report write. - One CLAUDE.md gotcha applied during verification: “Bash CWD drifts into worktree dirs after Read” — handled by reading worktree files via absolute paths and running git operations from main-repo working tree only.