Stage 1 — Docs and Tests

What this stage teaches

Stage 1 is the on-ramp. The skills are deliberately non-technical — they are the GitHub contribution workflow that every later stage assumes and never re-explains:

  • Claim an issue by commenting, then fork and branch.
  • Sign every commit off (DCO, git commit -s) so the Signed-off-by: line is present.
  • Add the one-line CHANGELOG.md entry that OpenSearch requires on every PR.
  • Run ./gradlew precommit and read its output without panicking.
  • Open a Pull Request, read the CI (GitHub Actions + Jenkins gradle-check), and respond to review by pushing more commits to the same branch.

The contributions themselves are surgical: a docs typo, a stale config key in a .md, a javadoc that the build flags, a weak or missing assertion in an existing unit test. Nothing in this stage will surprise a reviewer. That is the point — you are exercising the workflow so the next stages can be about code.

Note: If you are arriving from the Apache Tez roadmap: there is no JIRA, no .patch file, no "Submit Patch" button. The unit of work is a branch on your fork and a PR against opensearch-project/OpenSearch. Re-rolls are extra commits (or a force-push, with etiquette — see Pitfalls), not new attachments.


The GitHub workflow, end to end

Step 0 — Fork and clone

gh repo fork opensearch-project/OpenSearch --clone   # or fork in the UI, then:
git clone git@github.com:<you>/OpenSearch.git
cd OpenSearch
git remote add upstream https://github.com/opensearch-project/OpenSearch.git
git fetch upstream

Keep main clean and tracking upstream. Branch for every change:

git checkout -b docs/fix-rolling-upgrade-typo upstream/main

Step 1 — Find an issue

Paste this into the issue search box on the OpenSearch repo (https://github.com/opensearch-project/OpenSearch/issues):

is:issue is:open label:"good first issue" no:assignee sort:updated-desc

Narrow to docs/test work by adding a keyword:

is:issue is:open label:"good first issue" no:assignee docs in:title,body
is:issue is:open label:"good first issue" no:assignee "javadoc" in:title,body
is:issue is:open label:"good first issue" no:assignee "test" "assert" in:body

Open three candidates. Read each thread end to end. Choose one that has no assignee, no open PR linked, and is not labelled untriaged (untriaged means a maintainer has not confirmed it is real). Comment to claim it:

I'd like to work on this. Planning to <one sentence>. Could a maintainer assign it to me?

OpenSearch does not require an explicit assignment to open a PR, but commenting prevents two people doing the same work and signals the maintainers to triage.

If no labelled issue fits, file your own from a real defect you find. Grep the docs in the repo (most user docs live in the separate documentation-website repo, but the core repo carries README, CONTRIBUTING.md, DEVELOPER_GUIDE.md, TESTING.md, package-info javadoc, and inline javadoc):

grep -rn "cluster.initial_master_nodes" . --include=*.md --include=*.java | head
grep -rn "TODO\|FIXME\|XXX" DEVELOPER_GUIDE.md TESTING.md CONTRIBUTING.md
grep -rn "elasticsearch" docs/ 2>/dev/null | head     # stale fork references

A genuine doc or stale-reference bug found this way is fair game for your first PR. Open an issue describing it first, then the PR that fixes it (the PR links the issue with Closes #NNNN).


Walked example 1 — a javadoc / package-info documentation fix

This example is illustrative of the pattern, not a transcription of one specific PR. The grep finds the real site on your branch; class and line numbers vary by branch.

Symptom: a user reports on the forum that org.opensearch.common.settings.Setting has a public Property enum value whose javadoc does not say what it does, so plugin authors cannot tell when to use it.

Locate the code

grep -rn "enum Property" server/src/main/java/org/opensearch/common/settings/Setting.java
git log --oneline -n 5 -- server/src/main/java/org/opensearch/common/settings/Setting.java

Open the file. The relevant block looks roughly like:

public enum Property {
    Filtered,
    Dynamic,
    Final,
    Deprecated,
    NodeScope,
    IndexScope,
    // ...
}

Several values have no javadoc. That is the bug.

Diff

--- a/server/src/main/java/org/opensearch/common/settings/Setting.java
+++ b/server/src/main/java/org/opensearch/common/settings/Setting.java
@@
 public enum Property {
-    Filtered,
+    /** The setting's value is sensitive and must be redacted from APIs and logs. */
+    Filtered,
+    /** The setting can be changed at runtime via the cluster/index update settings API. */
     Dynamic,
+    /** The setting can be set once at index creation and never changed afterwards. */
     Final,

Two rules for documentation diffs:

  1. Describe behaviour, not the identifier. "Dynamic means dynamic" is noise. Say what changes for the user when the property is present.
  2. Verify against the code, not your memory. Trace where the enum value is read (grep -rn "Property.Dynamic" server/) and confirm your sentence matches the actual check before you write it.

Build the documentation gate

OpenSearch validates javadoc as part of precommit. For a docs-only change you do not need the full test suite — run the targeted gates:

./gradlew :server:compileJava -q          # does it still compile?
./gradlew :server:checkstyleMain -q        # style (line length, ordering)
./gradlew spotlessJavaCheck -q             # formatting
./gradlew :server:precommit                # the full per-project gate (javadoc, headers, forbidden-APIs)

If spotlessJavaCheck fails, fix it mechanically: ./gradlew spotlessApply.

CHANGELOG and commit

Every PR must add a line to CHANGELOG.md under the ## [Unreleased ...] heading, in the right category (Added / Changed / Fixed / Deprecated / Removed / Security):

--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ ## [Unreleased 3.x]
 ### Changed
+- Document the `Setting.Property` enum values for plugin authors ([#NNNNN](https://github.com/opensearch-project/OpenSearch/pull/NNNNN))

The PR number is not known yet; open the PR, then come back and fix the link in a follow-up commit (maintainers accept the placeholder being updated after the number exists).

Commit with sign-off:

git add server/.../Setting.java CHANGELOG.md
git commit -s -m "Document Setting.Property enum values"

The -s adds the Signed-off-by: Your Name <you@example.com> line that the DCO check requires. Configure user.name/user.email first so it matches your GitHub identity; the DCO bot compares them.

Open the PR

git push origin docs/document-setting-property
gh pr create --repo opensearch-project/OpenSearch --fill

Fill in the PR template: link the issue (Closes #NNNN), tick the CHANGELOG and DCO checkboxes honestly, and describe the change in two sentences. Then watch CI:

  • DCO — green only if every commit is signed off.
  • CHANGELOG verifier — green only if you touched CHANGELOG.md.
  • gradle-check / GitHub Actions — compile, precommit, and (for code) tests.

Read failures top to bottom. A docs PR that fails gradle-check almost always failed checkstyle or spotless, both of which the local commands above would have caught.


Walked example 2 — strengthening a weak unit-test assertion

Illustrative of the pattern. Run the grep to find a real candidate.

Symptom: an OpenSearchTestCase subclass exercises a parser but asserts only that the result is non-null — a regression that produced a wrong value would still pass. This is exactly the kind of issue tagged good first issue + test.

Locate a candidate

# Tests that assert non-null but never assert the value — a classic weak assertion smell:
grep -rn "assertNotNull" server/src/test/java/org/opensearch/ | head -40

Pick one where the parsed object has fields you can check. Read the test and the class under test together.

Diff

--- a/server/src/test/java/org/opensearch/common/unit/TimeValueTests.java
+++ b/server/src/test/java/org/opensearch/common/unit/TimeValueTests.java
@@
     public void testParseTimeValue() {
         TimeValue value = TimeValue.parseTimeValue("10s", "test");
-        assertNotNull(value);
+        assertNotNull(value);
+        assertEquals(10, value.seconds());
+        assertEquals(TimeUnit.SECONDS, value.timeUnit());
+        assertEquals("10s", value.getStringRep());
     }

Warning: Do not change the production class in a Stage 1 test PR. If strengthening the assertion reveals a real bug (the value is genuinely wrong), that is a Stage 3 bug fix — open a separate issue and PR. Mixing a test improvement with a behaviour change is the fastest way to get a Stage 1 PR bounced.

Run just this test

./gradlew :server:test --tests "org.opensearch.common.unit.TimeValueTests" -q
# reproduce a randomized failure later with the printed seed:
./gradlew :server:test --tests "org.opensearch.common.unit.TimeValueTests" -Dtests.seed=<SEED>

Then CHANGELOG (category Changed or no entry if the maintainers say test-only changes are exempt — check the PR template; OpenSearch generally still wants a line), commit with -s, push, open the PR.


Pitfalls

  • Scope creep. "While I was in the file I also fixed…" is the number-one reason a Stage 1 PR sits for months. One logical change, one CHANGELOG line. File a follow-up issue for everything else.
  • Missing CHANGELOG entry. The CHANGELOG verifier will fail your PR. Add the line in the correct category under ## [Unreleased ...], not under a released version heading.
  • Missing DCO. If you forgot -s, the DCO check fails. Fix the whole branch with git rebase --signoff upstream/main (or git commit --amend -s for a single commit), then force-push.
  • Force-push etiquette. Before a maintainer has reviewed, force-push freely to keep history clean. After review has started, prefer adding follow-up commits so reviewers can see what changed; squash at the end if asked. OpenSearch typically squash-merges, so you do not need a pristine history — you need a reviewable diff. Never force-push in a way that drops a maintainer's suggested change commit.
  • Editing user docs in the wrong repo. Most end-user documentation lives in opensearch-project/documentation-website, not the core repo. Confirm which repo owns the text before you open a PR; a doc fix in the wrong repo wastes a review cycle.
  • master vs cluster manager. If your docs fix touches node-role text, use cluster manager (formerly master) — do not reintroduce master-only wording.
  • Running the whole suite for a one-line change. ./gradlew check runs everything and takes a long time. For docs use the targeted gates above; for a single test use --tests.

Exit criteria — when you're ready for Stage 2

You can move on when:

  • You have one merged docs/javadoc PR and one merged test-only PR (a strengthened assertion or a missing @Test for an existing class).
  • You have responded to at least one round of reviewer nits by pushing follow-up commits, without a maintainer having to explain git commit -s or the CHANGELOG to you.
  • A green gradle-check no longer makes you anxious, and you can read a red one and tell whether the failure is yours or a pre-existing flake (Stage 9 territory).
  • You can recite the loop from memory: fork → branch → change → CHANGELOG → git commit -s./gradlew precommit → push → PR → read CI → respond with follow-up commits.

When to file a follow-up

If, while fixing a Stage 1 issue, you find a bigger problem — say the javadoc was missing because a setting was renamed without @Deprecated on the old key — do not bundle the bigger fix. File a follow-up issue:

While documenting Setting.Property (#NNNN) I noticed `index.foo` was renamed from
`index.bar` without an @Deprecated alias. Filing this to track the deprecation cleanup —
happy to take it as a Stage 2/3 change.

Narrow Stage 1 PR + follow-up issue is exactly what reviewers mean by "keep PRs focused." It is the discipline the entire roadmap depends on. Move on to Stage 2 — Build, Dependency, and Logging.