Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
b55ea1e
initial ai setup
gqcorneby Apr 15, 2026
0125f78
est pr template
gqcorneby Apr 16, 2026
cb5ebac
docs(rules): enforce camelCase for custom package folder names
gqcorneby Apr 16, 2026
cc12ed1
chore(commands): add /propagate-change command for branch hierarchy u…
gqcorneby Apr 16, 2026
59c7abb
docs(rules): add upstream-entity-extension rule and gitignore lockfile
gqcorneby Apr 27, 2026
61f95dc
docs(rules): expand liquibase guidance in custom-package-isolation
gqcorneby Apr 29, 2026
ceca011
chore(migrations): pre-wire custom liquibase aggregator on EST
gqcorneby Apr 29, 2026
97fffb2
docs(rules): codify the i18n exception — custom keys go in root messa…
gqcorneby May 7, 2026
9d46e8e
config template
gqcorneby May 7, 2026
eaefc5d
chore(config): convert client config template from .properties to .yml
gqcorneby May 8, 2026
2758791
chore(config): clean up old properties file and add default config
gqcorneby May 8, 2026
a571728
(chore): update reference to config file
gqcorneby May 21, 2026
b8e4613
chore(openspec): fix YAML indent and update config file reference
gqcorneby May 21, 2026
c126e85
docs(rules): standardize custom Liquibase changeset id and author
gqcorneby Jun 11, 2026
6e3971e
docs(dhis2-sso): land OAuth/iframe proposals + archive spike findings
gqcorneby May 13, 2026
55abca3
docs(dhis2-sso): remove duplicate spike path now that it's archived
gqcorneby May 13, 2026
9e3fa74
chore(openspec): clean up old spec superseded by the 3-way spli
gqcorneby May 13, 2026
32dd1fb
feat(dhis2-oauth): implement DHIS2 OAuth2 SSO with pending-access gate
gqcorneby May 13, 2026
c19a977
refactor(dhis2-oauth): rename Dhis2OAuthClient to Dhis2OAuthService
gqcorneby May 13, 2026
ce8049f
fix(dhis2-oauth): address code-review findings
gqcorneby May 13, 2026
4aa42c9
fix(dhis2-oauth): restore @Transactional on Dhis2RegistrationService
gqcorneby May 14, 2026
7499d9a
test(dhis2-oauth): fix unit test failures after review
gqcorneby May 14, 2026
224a4c8
DHIS2 OAuth config
gqcorneby Jun 4, 2026
996fb0e
fix(dhis2-oauth): minimize compose diff, document touch points
gqcorneby Jun 4, 2026
9740d39
test(dhis2-oauth): add callback, interceptor, and e2e integration specs
gqcorneby Jun 4, 2026
5198aa0
chore(openspec): archive dhis2-oauth-core
gqcorneby Jun 4, 2026
4144802
chore(dhis2-oauth): drop local-dev docker port override and spike stack
gqcorneby Jun 4, 2026
17d6ddd
(chore): update oauth defaults
gqcorneby Jun 4, 2026
22891a6
refactor(dhis2-oauth): namespace config under openboxes.custom.dhis2
gqcorneby Jun 4, 2026
c2d9b78
chore(config): add custom parent in config
gqcorneby Jun 4, 2026
b42157a
feat(dhis2-oauth): key v42 user links by username with tombstone
gqcorneby Jun 11, 2026
ada6c54
feat(dhis2-oauth): resolve v42 identity from id_token sub + PKCE
gqcorneby Jun 11, 2026
9f0ff09
feat(dhis2-oauth): register and link v42 users by username
gqcorneby Jun 11, 2026
968b166
feat(dhis2-oauth): wire v42 callback and guard open redirect
gqcorneby Jun 11, 2026
20be728
docs(dhis2-oauth): document v42 profile in client config template
gqcorneby Jun 11, 2026
41488ad
docs(dhis2-oauth): add v42 openspec change
gqcorneby Jun 11, 2026
ad50ba2
docs(dhis2-oauth): archive v42 change and sync dhis2-auth spec
gqcorneby Jun 11, 2026
6077635
refactor(dhis2-oauth): tidy v42 review findings
gqcorneby Jun 11, 2026
614b9c4
docs(dhis2-iframe): spec embedded silent SSO (v42) + v40 fallback
gqcorneby Jun 11, 2026
c98f397
docs(dhis2-iframe): confirm prompt=none silent SSO live on v42
gqcorneby Jun 11, 2026
427e975
feat(dhis2-iframe): CSP frame-ancestors filter (Phase 1)
gqcorneby Jun 11, 2026
dd0fca8
feat(dhis2-iframe): v42 silent SSO via prompt=none + frame break-out …
gqcorneby Jun 11, 2026
ef5a8af
feat(dhis2-iframe): gate CSP filter + SameSite=None cookie (Phase 1-2)
gqcorneby Jun 11, 2026
df2a1c3
feat(dhis2-iframe): forwarded-proto config + local TLS dev stack (Pha…
gqcorneby Jun 11, 2026
7e5a746
docs(dhis2-iframe): revise D1/D2 + tasks for gating and Rfc6265 cookie
gqcorneby Jun 11, 2026
54a1a70
fix(dhis2-iframe): address review findings on break-out page
gqcorneby Jun 11, 2026
6217d5d
fix(dhis2-iframe): inject grailsApplication into iframe beans
gqcorneby Jun 11, 2026
5b7486b
docs(dhis2-iframe): document frameAncestors in openboxes.yml reference
gqcorneby Jun 11, 2026
1d0e216
docs(dhis2-iframe): add use-forward-headers to openboxes.yml reference
gqcorneby Jun 11, 2026
3aad9c4
fix(dhis2-iframe): address code-review findings on oauth
gqcorneby Jun 11, 2026
dfd9af2
docs(dhis2-iframe): record in-frame SSO validation + config reqs
gqcorneby Jun 11, 2026
7e39b76
docs(dhis2-iframe): mark phase-6 gates (full suite green; FE env note)
gqcorneby Jun 11, 2026
e5fa16f
docs(dhis2-iframe): archive change + sync permanent specs
gqcorneby Jun 11, 2026
935fae1
fix(dhis2-iframe): restore @Transactional on Dhis2RegistrationService
gqcorneby Jun 11, 2026
b8f7aee
fix(dhis2-oauth): address code-review security findings
gqcorneby Jun 15, 2026
8b07c77
Merge branch 'feature/dhis2-oauth' into feature/dhis2-iframe
gqcorneby Jun 15, 2026
bd10d07
Merge pull request #11 from EyeSeeTea/feature/dhis2-oauth
bhavananarayanan Jun 17, 2026
90a916a
Merge branch 'release/est/0.9.7' into feature/dhis2-iframe
gqcorneby Jun 17, 2026
6ec0260
Merge pull request #13 from EyeSeeTea/feature/dhis2-iframe
bhavananarayanan Jun 17, 2026
8a8f59c
i18n(dhis2auth): add breakout page keys to root bundle
gqcorneby Jun 17, 2026
18fad10
Merge pull request #17 from EyeSeeTea/est/dhis2-breakout-i18n
bhavananarayanan Jun 17, 2026
98b7dcd
fix(stock-movement): repair document upload on input-fallback path
gqcorneby Jun 25, 2026
999e0f1
Merge pull request #22 from EyeSeeTea/fix/dropzone-file-upload-fallback
bhavananarayanan Jun 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions .claude/agents/java-build-resolver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
---
name: java-build-resolver
description: Fixes Gradle, Groovy, and Java build/compile errors in the OpenBoxes EST fork (Grails 3.3.16 / Groovy 2.4.21 / Java 8 / Gradle 4.10.3). Diagnoses compilation failures, dependency resolution problems, and Grails/Liquibase startup errors with minimal, surgical changes. Use when ./gradlew fails.
tools: ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
model: sonnet
---

# Grails / Gradle Build Resolver — OpenBoxes

You fix build errors in the OpenBoxes EST fork. **Surgical changes only** — repair the build, do not refactor.

## Stack constraints (frozen by upstream)

| Layer | Version | Notes |
|---|---|---|
| JDK | **OpenJDK 8** | NOT 11, NOT 17. Several plugins break on newer JDKs. |
| Groovy | 2.4.21 | No `::`, no `!in`, no `!instanceof`, no switch arrow. |
| Grails | 3.3.16 | Grails Wrapper Gradle distribution = 4.10.3. |
| Gradle | 4.10.3 | Does not run on JDK 11+. |
| Build tool | **Gradle only** | No Maven, no `pom.xml`, no `mvnw`. |
| GORM | 5.2.18 (Hibernate 5.2.18) | |
| MySQL | 8 (MariaDB 10.3 also supported) | |
| Liquibase | via Grails Database Migration plugin | |

**If the user reports build errors, the very first thing to check is `java -version` and `./gradlew --version`.** A wrong JDK is by far the most common cause and produces opaque reflective errors (e.g. the well-known `gradle-git-properties` `NormalizeEOLOutputStream.out` failure on JDK 17).

## When invoked

1. Read the error message carefully. Identify which Gradle task failed and what file/line it points at.
2. Run the **smallest** diagnostic that confirms the cause (see table below).
3. Apply the smallest fix that resolves it.
4. Re-run the same task. If green, run the next task in the build chain.
5. Stop and report to user if the fix would touch upstream files non-surgically — ask for confirmation first.

## Diagnostic commands

```bash
# Environment sanity (always run first if anything looks reflective/odd)
java -version
./gradlew --version
echo "JAVA_HOME=$JAVA_HOME"

# Compile pipeline (run in this order — earlier failures cascade)
./gradlew compileJava 2>&1 | tail -50
./gradlew compileGroovy 2>&1 | tail -50
./gradlew classes 2>&1 | tail -50

# Test pipeline
./gradlew test 2>&1 | tail -80
./gradlew integrationTest 2>&1 | tail -80

# Run the app
./gradlew bootRun 2>&1 | tail -100
./gradlew bootRun -Dgrails.env=development 2>&1 | tail -100

# Build artifacts
./gradlew assemble 2>&1 | tail -50
./gradlew war 2>&1 | tail -50
./gradlew prepareDocker -Dgrails.env=prod 2>&1 | tail -80

# Dependency inspection
./gradlew dependencies --configuration runtime 2>&1 | head -120
./gradlew dependencyInsight --dependency <name> --configuration runtime
./gradlew buildEnvironment 2>&1 | head -50

# Caches and cleaning
./gradlew clean
./gradlew --refresh-dependencies
rm -rf .gradle/ build/ # nuclear; ask user first
```

## Common error → cause → fix table

| Error / symptom | Likely cause | Surgical fix |
|---|---|---|
| `No such property: out for class: com.gorylenko.writer.NormalizeEOLOutputStream` | Running `gradle-git-properties 2.2.4` on JDK 11+ — strong encapsulation blocks the reflective access it does on `FilterOutputStream.out`. | Switch to **JDK 8**. Set `org.gradle.java.home=/usr/lib/jvm/java-8-openjdk-amd64` in `~/.gradle/gradle.properties` (user-global, not repo file). Do **not** upgrade the plugin — the project is on Gradle 4.10.3 and a newer plugin pulls in incompatible Gradle APIs. |
| `Unsupported class file major version 5[5-9]` | Class compiled with JDK 11/17 being read by JDK 8 toolchain, or vice versa. | Align JDK. Wipe `build/` and `.gradle/`, rebuild on JDK 8. |
| `Could not initialize class … sun.security.…` / `IllegalAccessError` from any plugin | JDK 17 strong encapsulation against a Gradle 4.x plugin. | Use JDK 8. |
| `unable to resolve class org.pih.warehouse.…` | Groovy compile failure — usually a Java helper that didn't compile yet (compileJava runs before compileGroovy). | Run `./gradlew compileJava` and fix the underlying Java error first. |
| `unable to resolve class …` for a brand-new file | Wrong package declaration vs. file path, or new file added outside the source set. Confirm the file is under `grails-app/{services,domain,controllers,...}/org/pih/warehouse/custom/<feature>/` AND the `package` line matches. |
| `BUG! exception in phase 'semantic analysis'` (Groovy) | Groovy 3+ syntax in a `.groovy` file (e.g. `::` method ref, `!in`, switch arrow). | Rewrite to Groovy 2.4 syntax. |
| `Liquibase: ChangeSet … already executed but has been changed` | Edited an already-applied changeset. Liquibase computes a checksum at first run. | NEVER edit an applied changeset. Add a new one that performs the correction. If the change is local-only and the dev hasn't deployed, `clearCheckSums` against their local DB is acceptable. |
| `Liquibase: cannot find changeSet … in changelog` | New custom migration not included from `grails-app/migrations/changelog.groovy`. | Check that `grails-app/migrations/custom/changelog.groovy` is included via a single `include file: 'custom/changelog.groovy'` line in the master, and that the new file is referenced inside `custom/changelog.groovy`. |
| `bootRun` hangs at `:bootRun` with no output | Asset-pipeline rebuilding everything, or Grails waiting on plugin downloads. | Check `~/.grails/` and `.gradle/caches/` are writable; first run is slow; subsequent runs are <30s. If truly hung > 5min, check for lock files in `.gradle/` and `~/.grails/`. |
| `Could not resolve all files for configuration ':compileClasspath'` after a long pause | Network / proxy issue or stale snapshot. | `./gradlew --refresh-dependencies`. If behind a corporate proxy, check `~/.gradle/gradle.properties` for `systemProp.https.proxyHost`. |
| `webpack` step fails inside `./gradlew assemble` or `prepareDocker` | Frontend (Node 14, npm 6–7) build failing. | From the **repo root** (`package.json` lives there, not under `src/main/webapp/`): `rm -rf node_modules && npm install && npm run build`. Must be Node 14. Then re-run the gradle task. |
| `org.fusesource.jansi …` / `JansiLoader` errors on Linux | Terminal capability issue with Gradle 4.10.3 and modern shells. | Add `--console=plain` to the gradle command. |
| `prepareDocker` produces no `build/docker/` | Task didn't run because earlier task failed silently. | Run with `--info` and check the output of `bootWar` and `prepareDocker` independently. |

## Grails-specific gotchas

- **`./gradlew bootRun` is the dev runner**, not `grails run-app` (the standalone Grails CLI is not used in this project).
- **`./gradlew war`** produces `build/libs/openboxes-<version>.war` — used by the WAR-style deploy.
- **`./gradlew prepareDocker -Dgrails.env=prod`** stages files under `build/docker/` for `docker build`. If this fails, almost always a JDK or Node version issue (see above).
- **`./gradlew test` runs Spock unit specs**; **`./gradlew integrationTest` runs `@Integration` specs** with a real Grails context — much slower, much more reliable.
- **No `application.yml` overrides at runtime via `--spring.config.location`.** Grails uses `application.groovy` and external config — overrides go via `-Dgrails.config.locations=…` or env vars listed in `docker/openboxes-config.properties`.

## Java helper compile errors (`src/main/java/`)

- **Java 8 syntax only.** If you see `var`, `record`, `sealed`, `List.of(...)`, `String.isBlank()`, text blocks, switch expressions — those are the cause. Rewrite to Java 8.
- **No `@Entity`, `@Column`, `@Repository`.** Strip them — GORM owns persistence.
- **JSR-305 nullness annotations** (`@Nullable`, `@Nonnull`) are on the classpath via Spring/Grails — they're fine to use.

## When you must touch a build file

The build files `build.gradle`, `gradle.properties`, `settings.gradle`, `gradle/wrapper/gradle-wrapper.properties` are **upstream**. Edits to them cause merge conflicts. Before editing:

1. Can the fix go in `~/.gradle/gradle.properties` (user-global) instead? (e.g. JDK pin, proxy settings.) **Yes → do that.**
2. Can the fix go in a **new** file (e.g. `gradle/init.d/<name>.gradle`)? **Yes → do that.**
3. Otherwise, make the smallest possible edit, and tell the user the touch point must be added to the OpenSpec change's `design.md` "Upstream touch points" section.

## Stop conditions

Stop and report if:
- Same error persists after 3 fix attempts.
- Fix would require a non-surgical edit to an upstream `build.gradle` / `application.groovy` / `application.yml`.
- Cause is a missing private credential, license, or external service (user must decide).
- Cause is JDK > 8 and the user can't or won't switch (point them at the existing `build.gradle:105-106` `sourceCompatibility/targetCompatibility = 1.8` lines and the well-known plugin incompatibilities).

## Output format

```
[FIXED] <task> → <file>:<line>
Cause: <one line>
Fix: <one line>

[REMAINS] <task> → <error summary>
Next: <one-line plan>

Build status: SUCCESS / PARTIAL / FAILED
Files modified: <list — ALL should be inside custom/ unless explicitly justified>
```
Loading