Using Valinor
Supported platforms
Valinor's methodology and governance spine apply to any stack today — the doctrine, the claims registry, the documentation gates, the compliance presence floors, and the LLM-review rubrics govern a repository regardless of the language it's written in. The deterministic gates that read a specific ecosystem — dependency health, the site-freshness projections, and the init scaffold — ship full support for TypeScript / Node, React Native / Expo, Swift / iOS, Kotlin / Android, Ruby on Rails, PHP / Laravel, and Python today; the remaining matrix languages (Go, Rust, Java) are detected and spine-governed while their ecosystem gates are generalized.
This page is the honest, current support status: what works end-to-end now, and what's in progress.
Read this as a status matrix, not a promise
Every row below states what is true today. A platform marked Expanding means the stack-agnostic spine already governs it, but one or more ecosystem-specific deterministic gates do not yet run end-to-end for it. We document the current tier rather than the intended one.
Support tiers at a glance
| Tier | What it means |
|---|---|
| Full support | Every layer runs end-to-end, including the ecosystem-specific deterministic gates (dependency health, site projections, init scaffold). |
| Expanding | The stack-agnostic spine (doctrine · claims · documentation gates · compliance presence · rubric review) governs the repo today; the ecosystem-specific deterministic gates are being generalized. Today this tier applies to the matrix languages not yet in the tables below — Go, Rust, and Java are stack-detected and spine-governed, with their ecosystem gates pending. |
Languages and runtimes
| Language / runtime | Tier | Notes |
|---|---|---|
| TypeScript / JavaScript (Node) | Full support | Valinor's own stack. Every gate — dependency health, site-freshness projections, the init scaffold, and the full rubric suite — runs end-to-end. |
| PHP / Laravel | Full support | The pipeline runs end-to-end: init auto-detects php (composer.json), the gates job runs the PHP leg (shivammathur/setup-php + composer install), the Composer dependency-health adapter is production-grade across all three legs (lock pin, the OR-aware unbounded grammar, composer audit with severity normalization), the audit inventory counts real PHP KLOC (string-aware; heredocs/nowdocs as content; #[Attribute] handled), Laravel compliance detection reads routes/** + Blade views, the version checks fall back to the latest release git tag for the no-version-in-manifest Packagist pattern, and the Railway / Heroku / AWS deploy legs verify commit-keyed. See the PHP / Laravel section below. |
| Python | Full support | The pipeline runs end-to-end: init auto-detects python (pyproject.toml / requirements.txt / setup.py), the dependency-health gate runs live (osv-scanner CVSS severity, pip-audit fallback) across pip / Poetry / uv with the scanner installed by the scaffold, the audit inventory counts real Python KLOC (#-only grammar; triple-quoted strings — docstrings included — as content), and the Railway / Heroku / AWS deploy legs verify commit-keyed. See the Python section below. |
| Swift | Full support | The pipeline runs end-to-end: init --stack swift scaffolds a macOS gates job (Xcode + the swift toolchain ship with the runner image), SwiftPM dependency health runs live in CI via the zero-secret Dependabot leg (the built-in token reads Dependabot alerts through the vulnerability-alerts: read workflow permission — no PAT, no App secret), the audit inventory counts real Swift KLOC (a nesting-aware, string-aware comment grammar), the review rubrics already reach *.swift (a11y / type-design / test-quality), and the App Store Connect deploy leg verifies commit-keyed. See the iOS / Swift section below. |
| Kotlin / Android | Full support | The pipeline runs end-to-end: init auto-detects kotlin (a root build.gradle(.kts)), the gates job runs the Gradle leg (JDK 21 + the OWASP dependency-check pre-generation), the audit inventory counts real Kotlin KLOC (.kt/.kts with a nesting-aware, string-aware comment grammar — the Kotlin spec's nested block comments handled exactly), the review rubrics reach *.kt/*.kts + Compose semantics + res/layout XML (a11y / type-design / test-quality), valinor release rolls the versionName in your build script, and the Google Play deploy leg verifies commit-keyed. See the Android / Kotlin section below. |
| React Native | Full support | The pipeline runs end-to-end: init auto-detects react-native (app.json + eas.json, or the expo/react-native dependency), the gates job runs the RN leg (Node 24, plus the android/ Gradle dependency-check slice when a bare workflow tracks one), the dependency surface is audited per ecosystem (npm + Gradle live; CocoaPods pin/range checked with its scanner gap a loud, acknowledgeable UNVERIFIABLE), the review rubrics reach Expo Router app/** screens, and the EAS deploy leg verifies commit-keyed. See the React Native / Expo section below. |
| Ruby on Rails | Full support | The pipeline runs end-to-end: init auto-detects rails (Gemfile + config/application.rb), the Bundler dependency-health gate runs live (osv-scanner severity, bundler-audit fallback) with the scanner installed by the scaffold, the audit inventory counts real Ruby KLOC (# + column-0 =begin/=end), and the deploy story is verified: the Railway and Heroku legs assert commit-keyed landed read-backs, composing with the release-phase migration gate and the /up healthcheck. See the Ruby on Rails section below. |
Package ecosystems
The dependency-health gate (OWASP A03 Software Supply Chain Failures — present lockfile · no known-critical vulnerabilities · no unbounded version specifiers) runs end-to-end for the npm-family ecosystems today and is being extended per-ecosystem.
| Ecosystem | Languages | Dependency-health gate |
|---|---|---|
| npm · yarn · pnpm · bun | TypeScript / JavaScript | Full support |
| Composer | PHP / Laravel | Full support — the adapter is production-grade across all three legs: the composer.lock pin check, the OR-aware unbounded-version grammar (platform requirements like php/ext-* excluded), and composer audit --format=json with severity normalization; where the audit tool is absent the gate reports a loud UNVERIFIABLE — never a silent pass. The init scaffold wires shivammathur/setup-php + composer install so the audit runs live. |
| pip · Poetry · uv | Python | Full support — osv-scanner (CVSS severity) with a pip-audit fallback; the init scaffold installs the scanner so the gate audits the lockfile live. |
| SwiftPM | Swift | Full support — GitHub Dependabot alerts, zero-secret: the scaffolded gates job reads them with the built-in GITHUB_TOKEN (vulnerability-alerts: read) and the gate normalizes them into a severity-graded report; pin (Package.resolved) + floating-ref checks run deterministically. Where the alerts cannot be read (Dependabot disabled · GHES) the gate reports a loud UNVERIFIABLE naming the cause — never a silent pass. |
| Gradle | Kotlin / Android | Full support — OWASP dependency-check (resolves the live graph); the init scaffold pre-generates the report (apply the org.owasp.dependencycheck Gradle plugin). The build script's versionName is also the version source release-check reads and valinor release / release-pr roll. |
| Bundler | Ruby / Rails | Full support — osv-scanner (CVSS severity) with a bundler-audit fallback; the init scaffold installs the scanner so the gate audits Gemfile.lock live. |
| CocoaPods | React Native (bare) / iOS | Pin + range legs run fully (a missing Podfile.lock or a constraint-less pod is flagged); the vulnerability leg is UNVERIFIABLE by design (no free CocoaPods scanner exists) — a loud blocking advisory you can consciously accept per-ecosystem via gates.dependency-health.ecosystems.cocoapods: acknowledged (printed every run, never a silent pass). |
Deploy targets
Valinor governs the repository and its CI quality bar; the deploy step itself is owned by your CI/CD. These are the targets the matrix is being generalized to cover, with their current status.
| Deploy target | Typical stack | Status |
|---|---|---|
| Vercel | TypeScript / Node | Full support |
| App Store Connect | Swift | Full support — the scaffolded deploy job derives a commit-keyed build number (CFBundleVersion = git rev-list --count HEAD), hosts your find-skills-wired upload (fastlane pilot or Apple's raw Build Upload API), and verifies the build landed: it polls /v1/builds until a build with exactly that number reaches processingState=VALID, re-minting the ES256 JWT every poll (ASC tokens cap at 20 minutes). App Store review outcome and "users can install" are human-gated — declared UNVERIFIABLE, never assumed green. Opt-in via the ASC_KEY_ID / ASC_ISSUER_ID / ASC_PRIVATE_KEY secrets + the ASC_APP_ID variable. |
| Google Play | Kotlin / Android | Full support — the scaffolded deploy job derives a commit-keyed, monotonic versionCode (git rev-list --count HEAD), hosts your find-skills-wired upload (fastlane or the raw Publishing API v3 — Gradle Play Publisher is in maintenance mode), and verifies the release landed with a post-commit re-read no upload tool ships: a fresh edits.insert + edits.tracks.get asserting versionCode membership in releases[].versionCodes and the release status (draft/halted never pass; staged rollouts report their userFraction). Review state and managed publishing have no API — declared UNVERIFIABLE, never assumed green. Auth is Workload Identity Federation + service-account impersonation (keyless, all repo variables); the SA JSON key is the flagged fallback. |
| EAS | React Native | Full support — the scaffolded deploy job publishes an EAS update and verifies it landed: the update group's gitCommitHash must equal the deployed commit, the channel must link the published branch, and the update's runtimeVersion must be served by a finished build (never green on trigger). Opt-in via the EXPO_TOKEN secret. |
| Railway | Any | Full support — the scaffolded deploy job hosts your find-skills-wired deploy (railway up --ci — the current CLI exits 0 only on SUCCESS; --detach is the named trigger-only anti-pattern) and verifies it landed with a GraphQL read-back: the latest deployment's status == SUCCESS and meta.commitHash equal to the deployed commit. meta is an untyped scalar and a CLI-tarball deploy carries no commitHash — that case is tolerated with a loud warning, never a silent pass. Set healthcheckPath (e.g. /up) — Railway gates activation on the probe, so SUCCESS also means the healthcheck passed. Auth is a project-scoped token via the Project-Access-Token header (not Bearer); opt-in via the RAILWAY_TOKEN secret + the RAILWAY_SERVICE_ID / RAILWAY_ENVIRONMENT_ID variables. |
| Heroku | Any | Full support — the scaffolded deploy job verifies the CURRENT release (newest ≠ current — a failed release leaves the previous one serving): status == "succeeded" && current == true, polled through pending (which auto-fails at 24h) with failed/expired failing closed, then commit-keyed via slug.commit for slug deploys; container deploys (null slug) use the baked /version marker fetched from the app URL — HEROKU_SLUG_COMMIT dyno-metadata is explicitly rejected as a primary key (a labs feature that can lag the release). The Procfile release: phase remains the built-in gate the read-back composes with. The secret shape is a scoped, expiring OAuth authorization (heroku authorizations:create) — never the permanent account key. |
| AWS | Any | Full support, as a declared-shape contract: declare your runtime shape — aws-ecs, aws-lambda, or aws-static — and the deploy job scaffolds a verified leg. ECS/Fargate: the deploy action pinned >= v2.6.2 (the version is the rollback detection — older versions read a circuit-breaker rollback as stable-green) + a read-back asserting the PRIMARY deployment's rolloutState == COMPLETED and the SHA-tagged image. Lambda: LastUpdateStatus == Successful + the commit SHA in the published version's Description + alias→version (never $LATEST). Static (S3 + CloudFront): a baked version.json SHA marker fetched through the distribution after wait invalidation-completed, cache-busted. Auth is GitHub OIDC via configure-aws-credentials — zero stored secrets (static AWS keys are the flagged anti-pattern). The framework targets (aws-cdk / aws-sam / aws-amplify) deploy arbitrary resource graphs, so they stay documented find-skills hooks with the landed-verification declared UNVERIFIABLE loudly. |
What "Expanding" already gives you
A repository on an Expanding platform (today: a Go, Rust, or Java repo) is not ungoverned. The stack-agnostic spine works for it today:
- The doctrine — your
AGENTS.md≡CLAUDE.mdcarries Valinor's versioned methodology and is verified against drift. - The claims registry —
file/grep/token/files-identicalclaims verify documentation and code facts in any language. - The documentation gates — dead-link, orphan, and stub detection run over your
.mdcorpus regardless of stack. - The compliance presence floors — the universal legal-doc and privacy-primitive checks apply by repo nature, not by language.
- The LLM-review rubric suite — Greptile reviews every pull request against the full rubric library; the rules reason about code and prose, not a specific runtime.
What's being generalized for these platforms is the set of ecosystem-specific deterministic gates — the dependency-health lockfile/vulnerability reader, the per-surface site projections, and the init scaffold's starter files.
Ruby on Rails
A Rails repository is a first-class governed stack. Adopt via the npx full-package form — the consumer path for every repo, with no install step (see Installation):
npx --yes @cmbrcreative/valinor@latest init . --stack rails
Pass --stack rails (or ruby) and the scaffold wires the Ruby-specific legs below.
Dependency health (Bundler)
The dependency-health gate (OWASP A03 Software Supply Chain Failures) audits your Gemfile / Gemfile.lock end-to-end:
- Pinned, reproducible installs — Bundler always writes a
Gemfile.lock, so a missing, unreadable, or malformed lock is a hard failure (installs are not reproducible). - No unbounded version specifiers — a
gemwith no constraint,*, or a>=/>-only lower bound is flagged; a pessimistic~>, an explicit upper bound, or an exact pin is accepted. VCS / path gems are excluded (their version is pinned by the lock revision). - No known vulnerabilities — the gate prefers osv-scanner (it reads
Gemfile.lockand carries CVSS severity) and falls back to bundler-audit. bundler-audit's severity is incomplete (ruby-advisory-db's CVSS is optional), so on the fallback path every advisory is counted as blocking, with a surfaced "severity-unavailable" note. If neither scanner is onPATHthe gate isUNVERIFIABLE— a loud, blocking advisory, never a silent 0-vulns pass.
The init scaffold installs osv-scanner (and bundler-audit) in the gates job, so the audit runs live by default rather than failing closed.
CI runtime
The scaffolded valinor-gates.yml uses ruby/setup-ruby@v1 with bundler-cache: true (it reads .ruby-version / your Gemfile; commit a .ruby-version to pin the interpreter) and runs the scanner-install step before the dependency-health producer, so the Bundler adapter has a scanner present.
Deploy verification (Railway / Heroku — verified legs)
Pass --deploy railway or --deploy heroku and the gates workflow's deploy job hosts your find-skills-wired deploy and verifies it landed — never green on trigger (see the deploy-targets table above for each contract: Railway's SUCCESS + meta.commitHash GraphQL read-back; Heroku's CURRENT-release succeeded + slug.commit keying). Two stack-native assertions compose with those read-backs:
- Heroku release-phase migration — set a
release:process in yourProcfile(release: bundle exec rails db:migrate). Heroku runs it after the slug is built but before the new release goes live; a failed migration aborts the release and the old one stays current — exactly the state the scaffolded read-back then detects (the current release'sslug.commitis not your commit). - The Rails
/uphealthcheck — Rails ships a built-in/upendpoint that returns HTTP 200 when the app has booted. On Railway, sethealthcheckPath: "/up"— the platform gates the cutover on the probe, so the verifiedSUCCESSstatus also means the healthcheck passed; on Heroku, curl/upas a post-release step.
Frame deploy the same way every Valinor stack does: mandate the outcome (an automated, gated path to the declared environment, verified landed), and select the tool per target via find-skills.
PHP / Laravel
A Laravel app (or any Composer-built PHP project) is a first-class governed stack. Adopt via the npx full-package form — the consumer path for every repo, with no install step (see Installation) — and you usually don't need a flag: init auto-detects php from a root composer.json and prints what it detected. Pass --deploy railway (or heroku, or an aws-* shape) to scaffold a verified deploy leg:
npx --yes @cmbrcreative/valinor@latest init . --stack php --deploy railway
The scaffolded gates job provisions PHP via shivammathur/setup-php and runs composer install before the dependency-health producer, so the audit runs live on the first run.
Dependency health (Composer)
The dependency-health gate (OWASP A03 Software Supply Chain Failures) audits your composer.json / composer.lock end-to-end:
- Pinned, reproducible installs — a missing, unreadable, or malformed
composer.lockis a hard failure. - No unbounded version specifiers — the grammar is OR-aware (
^8.1 || ^9.0is bounded) and excludes platform requirements (php,ext-*) from the contest; a*or a>=-only lower bound is flagged. - No known vulnerabilities —
composer audit --format=json, with advisory severities normalized into the gate's grading. If the tool can't run, the gate reports a loudUNVERIFIABLE— never a silent 0-vulns pass.
The audit corpus (real PHP KLOC)
The agent-driven audit's measured inventory counts PHP source with a string-aware grammar that gets the language's particulars right: // and # line comments, non-nesting /* */ blocks, heredocs/nowdocs counted as content (string bytes ship — they are source, not comments), and the PHP 8 #[Attribute] syntax recognized as code rather than a # comment. Blade templates (.blade.php) count too — template bytes ship.
Laravel-aware review and compliance
The L3 review rules reach **/*.php (Blade views included) and **/*Test.php; the compliance presence detection reads the Laravel shapes — routes/**/*.php and resources/views/**/*.blade.php (the double extension handled) — when checking the privacy-policy / ToS floors.
Versions and releases (the Packagist pattern)
Public Packagist syncs from your git tags — idiomatic Composer packages carry no version field in composer.json. The site-freshness version sync and release-check handle that honestly: a declared version is read where present, otherwise the latest release git tag is the version source (pre-releases excluded). Publishing to Packagist is the tag itself — the post-publish resolvability smoke for a private Composer registry is consumer-specific and declared UNVERIFIABLE rather than assumed green.
Python
A Python project (pip, Poetry, or uv) is a first-class governed stack. Adopt via the npx full-package form — the consumer path for every repo, with no install step (see Installation) — and you usually don't need a flag: init auto-detects python from pyproject.toml or requirements.txt and prints what it detected:
npx --yes @cmbrcreative/valinor@latest init . --stack python --deploy heroku
Dependency health (pip / Poetry / uv)
The dependency-health gate audits the Python dependency surface end-to-end: lockfile/pin checks where a lock exists, unbounded-specifier checks on the manifest, and the vulnerability leg via osv-scanner (CVSS severity) with a pip-audit fallback. The init scaffold provisions the Python runtime and installs the scanner in the gates job, so the audit runs live rather than failing closed. If neither scanner is available the gate reports a loud UNVERIFIABLE — never a silent pass.
The audit corpus (real Python KLOC)
The agent-driven audit's measured inventory counts Python source with the language's own grammar: # line comments only (Python has no block comments), string-aware (a # inside a string is code), and triple-quoted strings — docstrings included — counted as content: a docstring is shipped, runtime-introspectable bytes, never a comment to discount.
Deploy verification
Python web services deploy through the Any-stack verified targets: Railway (the SUCCESS + meta.commitHash GraphQL read-back; set healthcheckPath on the service), Heroku (the CURRENT-release succeeded + slug.commit keying; use a release: phase for migrations), or the AWS shapes (aws-ecs / aws-lambda / aws-static — OIDC-authenticated, commit-keyed read-backs). See the deploy-targets table above for each contract.
React Native / Expo
A React Native (Expo) repository is a first-class governed stack. Like any repo adopting Valinor, use the npx full-package form — the consumer path for every repo (see Installation) — and you usually don't need a flag at all: init auto-detects react-native from your tree (app.json + eas.json, or an expo/react-native dependency in package.json) and prints what it detected. Pass --stack react-native to be explicit, and --deploy eas to scaffold the verified EAS deploy leg:
npx --yes @cmbrcreative/valinor@latest init . --stack react-native --deploy eas
In a multi-repo workspace, valinor init-workspace runs the same detection per repo and records each repo's stack in valinor-workspace.yml — an RN app and a Node API in the same client folder each get their own correct scaffold.
Dependency health (managed vs. bare)
Your dependency surface depends on your workflow:
- Managed (CNG) apps —
ios/andandroid/are gitignored and prebuild-generated, sopackage.json+ your lockfile is the dependency surface. The npm audit covers it end-to-end; there is nothing else to audit (auditing a prebuild output is an anti-pattern). - Bare workflow — the tracked native trees are real dependency surfaces, audited per ecosystem in the same gate run: the
android/Gradle tree via OWASP dependency-check (theinitscaffold provisions the JDK and pre-generates the report — apply theorg.owasp.dependencycheckplugin inandroid/build.gradle), and theios/Podfilevia the CocoaPods adapter — its pin and version-range checks run fully, while the vulnerability leg is UNVERIFIABLE by design (no free CocoaPods scanner). That gap blocks loudly by default; accept it consciously — without dialing off the whole gate — with the per-ecosystem dial:
gates:
dependency-health:
ecosystems:
cocoapods: acknowledged # a loud advisory printed every run — never a silent pass
Pods, gems, and nested manifests are also reviewed at the LLM tier: the dependency-health review rule's scope covers **/Podfile, *.podspec, Gemfile, nested build.gradle, and nested package.json files, and the a11y/type-design/comment-coverage rules reach Expo Router app/** screens.
Deploy verification (EAS)
With --deploy eas, the gates workflow's deploy job publishes an EAS update and verifies it landed — never green on trigger:
- Commit-keyed — the published update group's
gitCommitHashmust equal the deployed commit ($GITHUB_SHA). - Reachable — the channel must link the published branch, and the update's
runtimeVersionmust be served by a finished build; a mismatch on either publishes an update no client ever receives (EAS exits 0 anyway — exactly the vacuous green the verification exists to catch). We recommendruntimeVersion: { policy: "fingerprint" }in your app config. - Honest store boundary — the optional store leg asserts the uploaded/submitted boundary (
eas submit --wait; usereleaseStatus: completedfor Play); "live in store" is human-review-gated, so it is declared UNVERIFIABLE rather than assumed green.
Setup is one secret, opt-in by name: provision an EXPO_TOKEN repo secret from a role-limited robot user on your Expo org. Without it the deploy job skips with a workflow warning.
iOS / Swift
An iOS app or Swift package is a first-class governed stack. Adopt via the npx full-package form — the consumer path for every repo, with no install step (see Installation):
npx --yes @cmbrcreative/valinor@latest init . --stack swift --deploy app-store-connect
The scaffolded gates job runs on macOS (Xcode and the Swift toolchain ship with the runner image — no setup action), and init fixes the classic misdetection: an iOS repo whose only root manifest is the fastlane/CocoaPods tooling Gemfile is detected as swift (the Podfile / *.xcodeproj / *.xcworkspace signals plus the counted Swift corpus), never as a Ruby project.
Dependency health (SwiftPM + CocoaPods)
Your dependency surface is audited per ecosystem in the same gate run:
- SwiftPM (
Package.resolved) — the pin leg hard-requires a committed, parseablePackage.resolved; a floating.branch(…)/.revision(…)pin is flagged. The vulnerability leg is real and zero-secret in CI: SwiftPM has no free lockfile scanner (OSV and Trivy don't cover it), so the gates workflow reads the repo's GitHub Dependabot alerts with the built-in token — the job carries thevulnerability-alerts: readpermission, writesdependabot-alerts.json, and the gate normalizes it into a severity-graded report. No PAT and no App secret; Dependabot also discovers Xcode-managedPackage.resolvedinside.xcodeproj/.xcworkspace. If the alerts can't be read (Dependabot disabled on the repo, or GitHub Enterprise Server where the permission hasn't shipped), the gate reports a loudUNVERIFIABLEnaming the cause and the remedy — enable Dependabot alerts in Settings → Advanced Security. - CocoaPods (
Podfile) — the pin and version-range checks run fully (a missingPodfile.lock, a floating git ref with no recorded commit, or a>=-only requirement is flagged); the vulnerability leg is UNVERIFIABLE by design (no CocoaPods advisory database exists anywhere — not Dependabot, not OSV). Accept that gap consciously — without dialing off the whole gate — viagates.dependency-health.ecosystems.cocoapods: acknowledged(a loud advisory printed every run, never a silent pass).
The audit corpus (real Swift KLOC)
The agent-driven audit's measured inventory counts Swift source with a nesting-aware, string-aware comment grammar (Swift block comments nest — /* /* */ */ is one comment; a */ inside a string literal is code), so an iOS repo's grade denominator is its real KLOC rather than a hard zero. Ruby tooling files (fastlane helpers, Gemfile-adjacent scripts) are counted with the Ruby grammar (# + column-0 =begin/=end).
Versions and releases (the tag IS the version)
A Swift package carries no manifest version field — Package.swift is executable Swift, and SwiftPM resolves a package's versions from its git tags. Valinor binds to that honestly:
- The site-freshness version sync and
release-checkresolve a Swift repo's canonical version from the latest release git tag (a pod library's*.podspecdeclared version is read where present). valinor release/release-prrollCHANGELOG.mdandRELEASES.mdnormally, skip the manifest edit with a printed note (there is nothing to bump — the release tag carries the new version), and the cut's tag is the version artifact.
Deploy verification (App Store Connect)
With --deploy app-store-connect, the gates workflow's deploy job verifies the build landed — never green on trigger:
- Commit-keyed by convention — an App Store Connect Build resource carries no git attribute, so the linkage is the CI-maintained convention
CFBundleVersion = git rev-list --count HEAD(deterministic and monotonic per commit). Your upload step stamps it; the verify step asserts against it. The convention itself is declared as such — not ASC-attested. - Processing-verified — the verify step polls
GET /v1/builds?filter[version]=<keyed>&filter[processingState]=VALIDuntil non-empty (30-second interval, ~20-minute window), re-minting the ES256 JWT on every poll (ASC tokens cap at 20 minutes). An upload that never finishes processing is invisible to TestFlight and the App Store — exactly the vacuous green the poll exists to catch. - Both upload shapes documented — fastlane (
app_store_connect_api_key+pilot) or Apple's raw Build Upload API (buildUploads→buildUploadFiles→ commit); the upload step is your find-skills-wired hook either way. - Honest boundaries — App Store review outcome/timing is human-gated and "users can install" exists only after review: both are declared
UNVERIFIABLEfrom CI. The optional TestFlight leg asserts the machine-readableexternalBuildState.
Setup is opt-in by name: the ASC API-key triplet (ASC_KEY_ID, ASC_ISSUER_ID, ASC_PRIVATE_KEY — the .p8 content) as repo secrets, plus the non-secret ASC_APP_ID repo variable. Without them the deploy job skips with a workflow warning.
Android / Kotlin
An Android app (or any Gradle-built Kotlin project) is a first-class governed stack. Adopt via the npx full-package form — the consumer path for every repo, with no install step (see Installation) — and you usually don't need a flag: init auto-detects kotlin from a root build.gradle(.kts) and prints what it detected. Pass --deploy play to scaffold the verified Google Play deploy leg:
npx --yes @cmbrcreative/valinor@latest init . --stack kotlin --deploy play
The scaffolded gates job runs on ubuntu with JDK 21 (Temurin) provisioned, makes gradlew executable, and pre-generates the dependency-health report.
Dependency health (Gradle)
The dependency-health gate audits the Gradle dependency surface end-to-end via OWASP dependency-check, which resolves the live dependency graph (no lockfile needed — gradle.lockfile is usually absent, and osv-scanner can only read one). The init scaffold pre-generates the JSON report in CI so the gate audits live on the first run. One prerequisite lives in your build (a CI step cannot inject a plugin): apply the org.owasp.dependencycheck plugin in build.gradle(.kts). Without it the gate reports a loud UNVERIFIABLE naming the remedy — never a silent pass. Lockfile/version-catalog pins are checked where present.
The audit corpus (real Kotlin KLOC)
The agent-driven audit's measured inventory counts Kotlin source (.kt + .kts) with a nesting-aware, string-aware comment grammar — Kotlin block comments nest per the language specification's recursive DelimitedComment rule (/* outer /* inner */ still a comment */ is one comment), a */ inside a string literal is code, and a """ raw string's interior lines count as source — so an Android repo's grade denominator is its real KLOC rather than a hard zero.
Review rubrics (Compose-aware)
The L3 review rules reach the Android UI idioms on both layers: Jetpack Compose (contentDescription, Modifier.semantics, Modifier.clickable with a role — the a11y rule names the Compose semantics modifiers explicitly) and classic XML layouts (res/layout/**, android:contentDescription / android:labelFor); type-design reads Kotlin sealed types, and test-quality reaches JUnit *Test.kt / src/test/**.
Versions and releases (the versionName roll)
A Gradle Android repo's canonical version is its build script's versionName declaration:
- The site-freshness version sync and
release-checkresolve the version from the firstversionName "x.y.z"(Groovy) /versionName = "x.y.z"(Kotlin DSL) declaration in the rootbuild.gradle(.kts); a script with noversionNamefails closed naming the remedy. valinor release/release-prroll that same declaration in place (style-preserving, every other byte untouched) alongsideCHANGELOG.mdandRELEASES.md— one authoritative version source, read side and write side bound to the same line.
Deploy verification (Google Play)
With --deploy play, the gates workflow's deploy job verifies the release landed — never green on trigger:
- Commit-keyed and monotonic —
versionCode = git rev-list --count HEAD(deterministic per commit on main; the monotonic derivation also kills the version-code-conflict flake class). Your upload step stamps it; the verify step asserts against it. - The post-commit re-read no upload tool ships — every existing tool (fastlane supply, Gradle Play Publisher, upload actions) stops at "edit committed". The scaffold re-reads the live track state through a fresh read-only
edits.insert+edits.tracks.get(track)and asserts versionCode membership inreleases[].versionCodes(never equality — a release carries every active code in a multi-APK/AAB setup). - Status asserted, not existence — a committed edit can still be a parked
draft(serving no users — the EAS-draft analogue) or ahaltedrollout: both fail, even if declared acceptable. A staged rollout (inProgress) must report itsuserFraction;completedis the fully-rolled state. Play's track-state API is the richest of the three store targets, so the asserted contract is stricter than EAS/ASC. - The error-silencer anti-pattern, documented — never set
changesNotSentForReview: trueto "fix" a failing commit; it turns review-pipeline errors into success-shaped non-deploys. - Honest boundaries — no API exposes review state at all, and managed publishing has no API surface (with it on, the release parks invisibly until a human clicks Publish in the Console): both declared
UNVERIFIABLE. The verification claims exactly "the release exists on track T in state S at fraction F". A first-ever release on any track is full-review-gated (hours–7 days); internal-track updates serve near-instantly.
Setup is keyless by preference: configure Workload Identity Federation + service-account impersonation and provision three repo variables — PLAY_PACKAGE_NAME (the applicationId), PLAY_WIF_PROVIDER, and PLAY_SA_EMAIL — no secret at all. The one-time ceremony is inviting the service account's email in Play Console (Users & permissions). The PLAY_SA_KEY JSON-key secret is the documented fallback, flagged on every run. Without credentials the deploy job skips with a workflow warning. For the upload itself, prefer fastlane (upload_to_play_store, WIF-native) or the raw Publishing API v3 — Gradle Play Publisher is in explicit maintenance mode.
Where this is heading
The generalization across the matrix is tracked in the Valinor repository's research ledger. For how the layers that already work fit together, see the Governance overview; for the exact gate behavior per command, see the CLI reference.
Next steps
- Ready to adopt on a supported stack? Walk through Installation.
- Not sure what your org needs provisioned first? See Prerequisites.
- Hit a snag? Check FAQ & troubleshooting.