diff --git a/README.md b/README.md
index 5ee60a2..f4f7e8d 100644
--- a/README.md
+++ b/README.md
@@ -199,6 +199,7 @@ forma-core API design: **F-020** ([`docs/forma-core-api.md`](docs/forma-core-api
forma-core extraction (restriction engine + TargetType + Android matrix kit): **F-021** done (`plugins/core`).
forma-core validation SPI + content rules (Gradle facade in `:validation`): **F-022** done.
forma-core TargetRegistry + Android DSL consumers: **F-023** done.
+Publish coordinates for `tools.forma:core` (library) + facade deprecation policy: **F-024** done (see [`docs/PLUGIN-PUBLISH.md`](docs/PLUGIN-PUBLISH.md)).
Icons made by Freepik
from www.flaticon.com
diff --git a/TICKETS.md b/TICKETS.md
index 166434b..8cad5bb 100644
--- a/TICKETS.md
+++ b/TICKETS.md
@@ -34,7 +34,7 @@ Update this file when picking or finishing work. Cron workers must pick the **hi
| F-021 | done | Extract dependency-type / restriction engine into `forma-core` | `plugins/core` + TargetType/NameMatcher/RestrictionGraph + AndroidTargetTypes + AndroidRestrictionKit (distinct jvm.library vs android.library); facades preserved. Related GH #39 |
| F-022 | done | Extract validation framework into `forma-core` | `plugins/core` TargetValidator + ContentRule; `:validation` facade; Android helpers call pure rules |
| F-023 | done | Wire Android implementation as first consumer of forma-core | TargetRegistry + AndroidTargetRegistry; DSL uses registry validators; sample green |
-| F-024 | todo | Publish/coordinate coordinates: `tools.forma:core` vs android plugins | |
+| F-024 | done | Publish/coordinate coordinates: `tools.forma:core` vs android plugins | Coordinates, docs, mavenLocal + validate, sample green |
## P3 — JVM applications
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index cb55d96..da3a850 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -328,7 +328,7 @@ Suggested extraction order (tickets F-020…F-024):
3. Validation SPI + content predicates in core; `:validation` thin facade — **done (F-022)**.
4. Wire Android DSL / registry as first consumer of core — **done (F-023)**
(`TargetRegistry` + `AndroidTargetRegistry`; sample green).
-5. Coordinates: e.g. `tools.forma:core` vs `tools.forma.android` (F-024).
+5. Coordinates + publish finalization: `tools.forma:core` (library) vs `tools.forma.android` + facades (F-024 done — see `docs/PLUGIN-PUBLISH.md`).
---
diff --git a/docs/PLUGIN-PUBLISH.md b/docs/PLUGIN-PUBLISH.md
index d0be431..c89bfa6 100644
--- a/docs/PLUGIN-PUBLISH.md
+++ b/docs/PLUGIN-PUBLISH.md
@@ -15,15 +15,84 @@ expected ownership model and credential flow so workers do not invent secrets.
| `plugins/:owners` | `tools.forma.owners` | Ownership metadata |
| `plugins/:config` | `tools.forma.config` | Settings store |
| `plugins/:deps` | `tools.forma.deps` | Dep model + catalogs |
+| `plugins/:core` | (library, no plugin id) | `tools.forma:core` Maven GAV (jar + sources jar) |
| `includer/` | `tools.forma.includer` | Separate build (own version) |
| `depgen/` | `tools.forma.depgen` | Separate build |
Current shared version for the `plugins/` multi-project: **`0.1.3`**
(see `formaPluginConfiguration` in `plugins/build.gradle.kts`).
+Core and plugins share the version (overridable for local testing via `-PformaLocalVersion`).
+
`:android`’s `publishPlugins` task depends on sibling `publishPlugins` so a
single `:android:publishPlugins` publishes the whole set.
+## Coordinates (F-024)
+
+**`tools.forma:core`** is a **library** (plain Maven jar), **not** a Gradle plugin.
+Android consumers continue to use the unchanged plugin id `tools.forma.android`.
+
+### GAV and plugin ids
+
+| Artifact | Coordinates | Type | Notes |
+|----------|-------------|------|-------|
+| forma-core | `tools.forma:core:0.1.3` (+ `-sources`) | Maven library | Single jar. Contains `tools.forma.core.target`, `.restriction`, `.validation`. |
+| Android | `tools.forma.android` (Plugin Portal) | Gradle plugin | Primary entrypoint. Depends on core (transitive). |
+| Facades (compat) | `tools.forma.target`, `.validation`, `.deps`, `.config`, `.owners` | Gradle plugins (thin) | Remain published; delegate/re-export core. |
+
+Group `tools.forma`, version `0.1.3` (shared source of truth in root `formaPluginConfiguration`).
+
+### Consumers
+
+- **Normal Android apps** (recommended): use the plugin — no direct core dep needed.
+ ```kotlin
+ plugins {
+ id("tools.forma.android") version "0.1.3"
+ }
+ ```
+- **Pure engine / future JVM or Bazel** (F-030+): depend directly on the library
+ when writing adapters that do not need the Android DSL:
+ `implementation("tools.forma:core:0.1.3")`.
+ (A future `tools.forma.jvm` plugin would also pull core.)
+
+### How plugin POMs declare the core dependency
+
+- Build files declare `implementation(project(":core"))` (and sibling facades).
+- During `maven-publish` (via `com.gradle.plugin-publish` for plugins):
+ matching GAV project dependencies are rewritten as external
+ `tools.forma:core:0.1.3` (and peer plugins) in the published POM.
+- `publishAllToMavenLocal` explicitly depends on `:core:publishToMavenLocal`
+ first so local resolution succeeds for plugin POMs and markers.
+
+### Single-jar confirmation (F-024)
+
+`tools.forma:core` is published as **one jar** (plus sources). There is no
+`core` + `core-gradle` split for the initial extraction.
+
+### Owners status (F-024)
+
+`:owners` stays a **sibling published plugin** (thin facade over metadata types).
+No forced merge of sources into the core jar during F-024.
+
+### Deprecation policy for facade plugin ids (F-024)
+
+- `tools.forma.target`, `tools.forma.validation`, `.deps`, `.config`, `.owners`
+ **remain published and supported** as thin facades for the 0.1.x series
+ (current minor + at least one subsequent minor).
+- **No removal or breaking change** to these ids in 0.1.x.
+- Deprecation (docs + Portal metadata) will be introduced later — e.g. after
+ JVM targets land (F-030) or after consumers have had a full minor of dual
+ availability (`tools.forma.android` + direct core).
+- Primary documented path: `id("tools.forma.android")` for Android; direct
+ `tools.forma:core` for non-Android engine consumers.
+- Facades provide a safe compat window; existing builds using them will not
+ break in 0.1.x.
+
+See also:
+- [`docs/forma-core-api.md`](forma-core-api.md) (design + resolved open questions)
+- [`docs/ARCHITECTURE.md`](ARCHITECTURE.md) §6 (extraction map updated for F-024)
+- Consumer usage stays `includeBuild` for development; mavenLocal for external repros.
+
## Forma-style publish configuration (GH #132)
Duplicated `rootProject.ext[…]` + per-module `gradlePlugin { … }` blocks are
@@ -210,5 +279,5 @@ recommended path for Forma development (see
| F-016 | This doc + `formaPluginConfiguration` / `formaPublishedPlugin` |
| GH #132 | Target-shaped publish config (implemented) |
| GH #133 | Shared Portal user (admin; documented, not automated) |
-| F-024 | Coordinate plan when `forma-core` is extracted (`tools.forma:core` vs android plugins) |
+| F-024 | Publish coordinates, consumer docs, POM wiring, deprecation policy for `tools.forma:core` vs facades (implemented) |
| F-018 | Gradle/AGP modernization; use local publish for upgrade smoke tests |
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 6ea570b..6b8272d 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -2,6 +2,34 @@
Newest entries first.
+## 2026-07-13 — F-024 Publish/coordinate coordinates: `tools.forma:core` vs android plugins
+
+- **Ticket:** F-024 → `done`
+- **Branch:** `forma/F-024-publish-coordinates` (from `origin/v2`)
+- **Skills/modes:** /goal + todo_write + direct implement (per user query); self-verify via spawned verifier subagent
+- **Docs (primary):**
+ - Added dedicated **Coordinates (F-024)** section to `docs/PLUGIN-PUBLISH.md`: GAV matrix, single-jar confirmation, `:owners` sibling status, POM wiring (project → external GAV), consumer guidance (android plugin vs pure core), deprecation policy (keep facades through 0.1.x, no removal).
+ - Updated table of published artifacts to include `:core`.
+ - Resolved open questions in `docs/forma-core-api.md` §11; updated extraction checklist + F-024 row.
+ - Updated `docs/ARCHITECTURE.md` step 5 extraction map.
+ - README Progress section + cross links.
+- **Publish wiring / metadata:**
+ - Hardened `plugins/core/build.gradle.kts`: added `pom { name, description, url, scm }` (matches forma config; visible in published core POM).
+ - `publishAllToMavenLocal` already orders core first (confirmed in execution).
+ - No changes to turn `:core` into a plugin; no plugin-publish applied to core.
+- **Verify (real output, OpenJDK 17 via env-mac.sh):**
+ - `plugins/`: `./gradlew :core:test` → **BUILD SUCCESSFUL**
+ - `plugins/`: `./gradlew build` → **BUILD SUCCESSFUL** (68 tasks)
+ - `plugins/`: `./gradlew publishAllToMavenLocal -PformaLocalVersion=0.1.3-F024` → **BUILD SUCCESSFUL**
+ - `~/.m2/.../core/0.1.3-F024/` has `core-0.1.3-F024.jar` + `-sources.jar` + `.pom` (with metadata)
+ - android POM contains `core0.1.3-F024` (project dep substituted)
+ - `plugins/`: `./gradlew :android:publishPlugins --validate-only` → packaging tasks ran; failed only on missing `gradle.publish.key/secret` (explicitly acceptable per AC)
+ - `application/`: `./gradlew :binary:assembleDebug` → **BUILD SUCCESSFUL** (578 tasks)
+- **Grounded:** all commands executed; outputs captured; no invented green builds.
+- **Commits/PRs:** commit b215f01 on `forma/F-024-publish-coordinates`; PR https://github.com/formatools/forma/pull/167 (base v2); self-verification PASS
+- **Blockers:** none
+- **Next step:** F-030 JVM targets (after PR merge)
+
## 2026-07-13 — F-023 Wire Android as first consumer of forma-core
- **Ticket:** F-023 → `done`
diff --git a/docs/forma-core-api.md b/docs/forma-core-api.md
index f5b03bb..b4769e4 100644
--- a/docs/forma-core-api.md
+++ b/docs/forma-core-api.md
@@ -405,7 +405,7 @@ Bazel adapter (later): same registry + restriction graph; replace
| **F-021** | Create `plugins/core` (or move packages); target types + **restriction graph** + wire Android matrix data; keep binary/API facades so sample builds |
| **F-022** | **Done:** validation SPI, identity-cached factories, content predicates in `plugins/core`; Android helpers call core; `:validation` facade |
| **F-023** | **Done:** Android DSL uses registry; duplicated allow-lists removed from entrypoints; sample green |
-| **F-024** | Publishing coordinates, README/Portal metadata, deprecate old plugin jars if merged |
+| **F-024** | Publishing coordinates (`tools.forma:core` library GAV + facade policy), consumer docs, POM dep wiring, deprecation plan (no removal in 0.1.x); single-jar + owners status documented |
Do not skip to JVM targets (F-030) until F-023 is done.
@@ -527,15 +527,21 @@ an Android SDK.
---
-## 11. Open questions (resolve during F-021, not blockers for design accept)
-
-1. Single jar `tools.forma:core` vs `core` + `core-gradle` split in the first
- extraction PR — **default: single jar** with gradle package.
-2. Whether `:owners` merges into core jar or stays a one-class sibling — **default: merge**.
-3. Exact deprecation timeline for `tools.forma.target` / `.validation` plugin
- ids on Portal — decide in F-024 with publish docs.
-4. Whether `EdgeKind.TEST` project validation should turn on with a flag —
- default **off** (parity).
+## 11. Open questions (resolved in F-024)
+
+1. **Single jar**: confirmed. `tools.forma:core` is one Maven artifact (jar + sources).
+ No `core-gradle` split.
+2. **Owners**: stays sibling published plugin for now (thin facade). No forced merge
+ into the core jar (documented in `docs/PLUGIN-PUBLISH.md`).
+3. **Deprecation timeline for facade ids** (`tools.forma.target`, `.validation`, `.deps`,
+ `.config`, `.owners`): remain published and supported through 0.1.x (current + at
+ least one more minor). No removal in 0.1.x. Deprecation signalled later (e.g. post
+ F-030 JVM or after documented dual-availability period). See full policy in
+ `docs/PLUGIN-PUBLISH.md` § Coordinates (F-024).
+4. `EdgeKind.TEST`: still deferred (parity with current behavior; off by default).
+
+F-024 also finalized publish coordinates, POM wiring, and consumer guidance
+(`tools.forma:core` library vs `tools.forma.android` plugin).
---
diff --git a/plugins/core/build.gradle.kts b/plugins/core/build.gradle.kts
index e8899f3..aafa369 100644
--- a/plugins/core/build.gradle.kts
+++ b/plugins/core/build.gradle.kts
@@ -27,6 +27,20 @@ publishing {
groupId = project.group.toString()
artifactId = "core"
version = project.version.toString()
+
+ pom {
+ name.set("forma-core")
+ description.set(
+ "Forma core library (not a Gradle plugin): TargetType, RestrictionGraph, " +
+ "TargetValidator / ContentRule, TargetRegistry. Co-versioned with tools.forma.* plugins."
+ )
+ url.set("https://forma.tools/")
+ scm {
+ connection.set("scm:git:git://github.com/formatools/forma.git")
+ developerConnection.set("scm:git:ssh://github.com/formatools/forma.git")
+ url.set("https://github.com/formatools/forma")
+ }
+ }
}
}
}