Skip to content

Add a skill-shipped grounding gate and the spike findings - #44

Draft
baonguyenNava wants to merge 3 commits into
baonguyenNava/30-strata-qa-clifrom
baonguyenNava/42-claude-tag-gate-spike
Draft

Add a skill-shipped grounding gate and the spike findings#44
baonguyenNava wants to merge 3 commits into
baonguyenNava/30-strata-qa-clifrom
baonguyenNava/42-claude-tag-gate-spike

Conversation

@baonguyenNava

@baonguyenNava baonguyenNava commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

What

Delivers the spike in #42: a skill that answers Strata documentation questions with every quote verified against its cited source, plus the findings on whether it can replace the Lambda transport #40 designs.

The port is faithful and the deployment surface disappears, but the guarantee that the check actually runs does not survive and cannot be recovered by code. That tradeoff is the finding, and it turns the remaining question into a product judgment rather than an engineering one.

Relates to #42

How

Three stages, two of them deterministic programs rather than model judgment:

select-candidates.mjs  ->  the model proposes an answer  ->  verify-answer.mjs
   (deterministic)              (the only model step)         (deterministic)

verify-answer.mjs is a dependency-free port of strata-qa/src/grounding.ts. The model proposes citations; the script checks each quote appears verbatim in the file it cites and demotes the verdict when it does not. The answer text is released only on answered, exactly as run.ts does it.

select-candidates.mjs scores docs/graph.json instead of walking the filesystem. Because the graph indexes only the 56 documents under docs/sources/, .verification, .curation, and superpowers are excluded by data structure rather than by the .dockerignore rules the image depends on. Selection can only cost recall, never soundness, since the gate rechecks every quote regardless.

Scripts live under bin/ rather than workflows/ on purpose. The .mjs files under skills/generate-strata-docs/workflows/ are Workflow tool orchestration scripts using ambient agent() and phase() globals, and cannot be run with node. Two kinds of artifact, two directories.

Nothing under strata-qa/ changed, and no dependency was added to the repository.

Test plan

  • node --test "skills/answer-strata-question/bin/*.test.mjs": 33 pass, 0 fail. That is 32 in verify-answer.test.mjs (all 20 cases ported from grounding.test.ts, plus 12 for the CLI surface, including assertions that low_confidence and no_match never leak the proposed answer), and a 33rd from differential.test.mjs, whose single test is the differential sweep described in the next bullet (500 cases per run at its defaults, widened for the numbers below).
  • Differential harness against the original TypeScript ground(): 20,000 generated cases across seeds 20260728, 1, 99991, and 424242, with zero disagreements on the full GroundingResult. Reproduce one seed at a time with CASES=5000 SEED=1 node --test skills/answer-strata-question/bin/differential.test.mjs, which is 5,000 of the 20,000; the full run is that command once per seed.
  • Fault injection to prove the harness can fail: replacing per-document gating (distinctDocs < docsCited) with per-quote gating (quotesVerified < citationsTotal) was caught in 53 of 500 cases and by one unit test. Reverted, and both suites returned to green.
  • Manual end-to-end run on the real corpus: a faithful quote returns answered with a verdict line and exit 0; changing one character of that quote returns no_match with the answer withheld and exit 2; adding an unverifiable second document returns low_confidence and exit 1; prose with no JSON exits 6.
  • Candidate selection measured over all nine golden fixtures: of 56 indexed documents, a question matches 17.0 on lexical scoring and 7.1 at --limit 8, both means across the nine, which is the table in the findings spec. "What is the best pizza topping?" scores zero and is refused before any document is read.
  • Nothing else regressed: python -m pytest 65 passed, MANIFEST_OK / DOCS_OK / GRAPH_OK with no graph diff, and strata-qa still at 213 passing tests.

Notes for reviewers

This is stacked behind #38 and #41. Base is baonguyenNava/30-strata-qa-cli, the same base #41 targets, so the diff carries #41's commit as well: the 2026-07-27 design spec and the .dockerignore comment change that came with it. Review those in #41; what is new here is skills/answer-strata-question/ and the findings spec. The findings commit amends the 2026-07-27 spec's status line, which is why both PRs touch that file. Merge order is #38, then #41, then this, and the overlap drops out of this diff once #41 lands.

#42 should stay open. Its Output section asks for a decision logged on the issue, and that decision is yours, not this PR's. The findings deliberately stop at presenting the tradeoff.

The limitation we cannot close. Control is inverted, and no amount of code un-inverts it. In the Lambda, trusted code wraps the untrusted model; in a skill, the agent calls the trusted code. Neither auto mode allow rules (which permit rather than compel) nor repository hooks (which cannot gate the Slack post, since posting is the harness's output channel) fix that. The mitigation is that the gate writes the final message and stamps a verdict line, so a skipped gate is visible rather than prevented. Prevention became detection, and that is a downgrade worth naming.

Deliberate duplication. ground() now exists twice. Deduplicating was out of scope for a spike, and the differential harness keeps it safe by failing the moment the two diverge. The findings recommend making the .mjs the source of truth if this direction is adopted, and deleting the skill outright if it is not.

Unmeasured. Whether Claude Tag reliably invokes the gate, end-to-end answer quality, real cost, and whether the eight-candidate cap costs recall all need a live workspace and a Team or Enterprise entitlement. Confirming that entitlement is the cheapest next step and blocks the rest.

Small wrinkle worth keeping if this gets CI wiring. Use the glob, not the bare directory: node --test <dir> also executes verify-answer.mjs, which reads empty stdin and exits non-zero.

baonguyenNava and others added 3 commits July 27, 2026 18:40
Issue #30 deferred the Slack bot itself, so the transport from a Slack
channel to the deployed QA Lambda was never designed. Two facts force the
shape: Slack's 3-second ACK deadline against a 9-90s answer, and the
RESERVED_CONCURRENCY=3 spend ceiling that a shared ACK/work pool would
collapse under load. Hence a thin public gateway in front of the existing
IAM-only function, both from one image.

Records the reasoning so the implementing PR argues about code, not
architecture. Also updates the .dockerignore comment that claimed nothing
under docs/superpowers was in git — this commit makes that false, and the
exclusion is now a real one rather than insurance.

Relates to #30

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Spike #42 asks whether a Claude Tag skill can preserve the guarantee
strata-qa enforces without deploying the Slack gateway #40 designs. The
gate is the part that has to survive the move, so it is the part this
ports: the model proposes citations, and code decides whether they
verify.

The port is dependency free and runs on Node's built-in test runner, so
the skill adds nothing to this repository's dependency surface. It sits
under bin/ rather than workflows/ because the .mjs files under
skills/generate-strata-docs/workflows/ are Workflow tool orchestration
scripts that use ambient agent() and phase() globals and cannot be run
with node. Two kinds of artifact, two directories.

A port claim is worth nothing unverified, so differential.test.mjs runs
generated cases through both implementations and compares the whole
GroundingResult. Its own detection power was checked by injecting the
per-quote versus per-document error the original's comments warn about,
which it caught in 53 of 500 cases.

Candidate selection scores graph.json rather than walking the
filesystem, which excludes .verification, .curation, and superpowers by
data structure instead of by the .dockerignore rules the image relies
on. It can only cost recall, never soundness, because the gate rechecks
every quote regardless of what selection handed the model.

Relates to #42

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C81WXLzPWLPryrEPsjZ6BC
The port is faithful. Twenty thousand generated cases across four seeds
produced zero disagreements with the original, on a harness whose
detection power was demonstrated rather than assumed. Corpus scoping
comes out stronger than the Lambda's, and the deployment surface, the
Cursor dependency, and corpus staleness all disappear with it.

What does not survive is the guarantee that the check runs at all. In
the Lambda, run.ts calls ground() unconditionally, so trusted code wraps
the untrusted model. In a skill the agent decides. That inversion is not
closable by code, only observable, which turns the remaining question
into a product judgment rather than an engineering one.

Amends the 2026-07-27 design spec status so an approved spec is not
silently orphaned while that judgment is pending.

Relates to #42

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C81WXLzPWLPryrEPsjZ6BC
@baonguyenNava
baonguyenNava changed the base branch from baonguyenNava/40-strata-qa-slack-bot to baonguyenNava/30-strata-qa-cli July 28, 2026 22:54
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