feat(toolchain): declare gate.language so a mislabelled repo keeps its spec truthful - #254
Conversation
…s spec truthful The manifest chain reads build orchestration, so a repo whose product language differs from its build host (a C++ SDK driven by Gradle) is mislabelled by construction. TECH_STACK_MISMATCH now cross-checks spec.project.language against .cladding/config.yaml::gate.language when declared, and still warns when the two disagree.
…ry behind it The declaration replaced the manifest verdict silently, so a stale declaration and a legitimate build-host mismatch produced the same empty finding list. Nothing mechanical separates the two, which makes the override a waiver — so it is now disclosed at info severity (never gate-failing, including under --strict) naming both labels and which one is in force. F-013's AC-021 claimed an unconditional warn on any manifest disagreement, which the declaration had made untrue; it is now scoped to the no-declaration case. The declaration itself gains a spec entry with its own criteria rather than riding on a detector it contradicted. Also repairs the release-record breakage the change introduced: README test counts across all six variants, and the Claude plugin bundle, which the build regenerates from source. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
8324160 to
0b9d4de
Compare
A simulation against both versions showed the claim was wrong, not the code. The gate's terminal renderer surfaces errors, else warns — info is never in that set — and SARIF excludes info by contract, so "legible in every gate log" described a line that never appears in one. The record reaches `clad check --json` and clad_run_check(verbose), which is an auditor's surface, not the developer's screen. Measured, not assumed: with a declaration in force over a differing manifest, the pre-change detector returns nothing at all and this one returns the info finding, in JSON, in both the legitimate build-host case and the stale-declaration case — it records that an override happened, and deliberately does not claim to tell those two apart. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Thank you for this PR — and for the diagnosis inside it, which turned out to be more right than the fix. Your problem statement was exact: the manifest chain reads build orchestration, so a repository whose product language differs from its build host is mislabelled by construction. When we put that through an external test harness (packed binary, isolated install, a faithful clone of the Gradle-driven C++ SDK shape), it reproduced immediately — and it went further than an edge case: What shipped instead (released today as v0.9.4 — Measured on your exact shape, through the shipped binary: the Gradle+CMake C++ SDK and an npm-shipped Rust core both pass Two things from this PR's journey also shipped on their own merits: the alternatives analysis you wrote (which consumers Closing this PR as superseded rather than merged: the residual case a declaration could still serve — a truthful language claim with zero in-tree sources of that language (out-of-tree or generated code) — is narrow, and we'd rather design for it against a real repository than ship a key speculatively. If your actual SDK hits that shape on 0.9.4, please reopen with that evidence and we'll build it directly on top of the new detector. Thanks again — this is the kind of PR that improves the project even when its diff doesn't land. |
Problem
The manifest chain reads build orchestration, so a repository whose product language differs from its build host is mislabelled by construction. Real case that motivated this: an Android RASP SDK whose products are C++ (built by CMake under Gradle — the root manifest is
build.gradle), sodetectToolchainresolvesjavawhilespec.project.language: cpptells the truth.TECH_STACK_MISMATCHthen stays red under--strict, and the only way to green it was rewriting the spec to adopt the mislabel — exactly the "rewrite the claim to match the check" anti-pattern the harness warns about elsewhere.Change
.cladding/config.yaml::gate.language— optional declared language label (trimmed string; non-string/empty ignored), parsed ingate-config.tsfollowing the existingscope/coverage/test_reportpattern.TECH_STACK_MISMATCH— when a declaration is present, cross-checksspec.project.languageagainst the declaration instead of the manifest heuristic, and still warns when the two disagree, so the check keeps its teeth. A matching declaration also anchors the no-manifest case (no more "cannot be cross-checked" info).Alternatives considered
build.gradle, so the chain resolvesjavabefore any cmake entry could match.detectToolchain— larger blast radius: that function also feeds gate-command selection, and returning a language that has no toolchain entry would surprise those consumers.Verification
gate.language: cppdeclared,clad check --tier=pre-push --strictgoes 9/9 pass/skip whilespec.project.language: cppstays truthful; removing the declaration bringsTECH_STACK_MISMATCHstraight back (negative control).npm run typecheck✓ ·npm run lint✓ · CHANGELOG entry under[Unreleased].npxENOENT); no new failures introduced.npm run conformancecould not run locally (the runner'ssymlinkSynchits EPERM without Windows Developer Mode) — deferring that to CI.Notes for review
gate.languagewas chosen to sit beside the existing gate overrides; happy to rename (e.g.toolchain.language) or relocate if you'd rather anchor it differently.🤖 Generated with Claude Code