Skip to content

feat(toolchain): declare gate.language so a mislabelled repo keeps its spec truthful - #254

Closed
Haaileyyy wants to merge 3 commits into
qwerfunch:developfrom
Haaileyyy:feature/toolchain-language-declaration
Closed

feat(toolchain): declare gate.language so a mislabelled repo keeps its spec truthful#254
Haaileyyy wants to merge 3 commits into
qwerfunch:developfrom
Haaileyyy:feature/toolchain-language-declaration

Conversation

@Haaileyyy

Copy link
Copy Markdown

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), so detectToolchain resolves java while spec.project.language: cpp tells the truth. TECH_STACK_MISMATCH then 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 in gate-config.ts following the existing scope/coverage/test_report pattern.
  • TECH_STACK_MISMATCH — when a declaration is present, cross-checks spec.project.language against 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

  • A cpp/CMake toolchain entry — doesn't help this class of repo: the root manifest is build.gradle, so the chain resolves java before any cmake entry could match.
  • Overriding inside detectToolchain — larger blast radius: that function also feeds gate-command selection, and returning a language that has no toolchain entry would surprise those consumers.
  • A waiver — silences the check instead of re-anchoring it.

Verification

  • Unit: +5 tests (matching declaration silences a manifest mismatch · declaration-vs-spec disagreement still warns · no-manifest case covered · parse trims · rejects non-string/empty) — 23/23 across the two touched test files.
  • E2E on the motivating repo: with gate.language: cpp declared, clad check --tier=pre-push --strict goes 9/9 pass/skip while spec.project.language: cpp stays truthful; removing the declaration brings TECH_STACK_MISMATCH straight back (negative control).
  • npm run typecheck ✓ · npm run lint ✓ · CHANGELOG entry under [Unreleased].
  • Full suite on this Windows box: 88 failures with the change vs 90 on clean develop — the same pre-existing environment set (path separators, CRLF assertions, 8.3 short paths, npx ENOENT); no new failures introduced. npm run conformance could not run locally (the runner's symlinkSync hits EPERM without Windows Developer Mode) — deferring that to CI.

Notes for review

  • Key name gate.language was chosen to sit beside the existing gate overrides; happy to rename (e.g. toolchain.language) or relocate if you'd rather anchor it differently.
  • Skipped the pre-PR issue since the change is small and fully scoped here — happy to split the discussion out into one if preferred.

🤖 Generated with Claude Code

Haaileyyy and others added 2 commits August 26, 2026 12:04
…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>
@qwerfunch
qwerfunch force-pushed the feature/toolchain-language-declaration branch from 8324160 to 0b9d4de Compare August 26, 2026 03:14
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>
@qwerfunch

Copy link
Copy Markdown
Owner

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: clad init itself seeds language: cpp on that tree, and the detector then rejected the very label cladding had just written. Two of three realistic subjects hit it. That reframed the work from "add an escape hatch" to "fix the model."

What shipped instead (released today as v0.9.4 — npm update -g cladding gets it): TECH_STACK_MISMATCH no longer consults the manifest chain for identity. It judges the observed sources: a language it doesn't know, or a tree with under five classified files, produces silence; a declared language absent from the sources still warns, with the evidence in the message; a minority presence is disclosed at info and never blocks. Gate-command selection keeps using the manifest chain — your PR body's warning about touching detectToolchain's other consumers was correct, and that boundary held.

Measured on your exact shape, through the shipped binary: the Gradle+CMake C++ SDK and an npm-shipped Rust core both pass --strict with no .cladding/config.yaml, no gate.language, and spec.project.language kept truthful. The declaration this PR adds no longer has a case to unlock.

Two things from this PR's journey also shipped on their own merits: the alternatives analysis you wrote (which consumers detectToolchain feeds) shaped the final design, and the E2E run against this branch exposed that .cladding/config.yaml could never be committed at all (.gitignore used the directory form, which git cannot re-include under) — fixed, with clad doctor now reporting legacy projects.

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.

@qwerfunch qwerfunch closed this Aug 26, 2026
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.

3 participants