Align contributing guidelines (#209)

diff --git a/landing-page/content/common/contribute.md b/landing-page/content/common/contribute.md
index 23961e4..2761657 100644
--- a/landing-page/content/common/contribute.md
+++ b/landing-page/content/common/contribute.md
@@ -83,6 +83,71 @@
 [IntelliJ](https://github.com/google/google-java-format#intellij-android-studio-and-other-jetbrains-ides) to install the **google-java-format** plugin (note the required manual actions for IntelliJ).
 
 
+## Semantic Versioning
+
+Apache Iceberg leverages [semantic versioning](https://semver.org/#semantic-versioning-200) to ensure compatibility
+for developers and users of the iceberg libraries as APIs and implementations evolve.
+The requirements and guarantees provided depend on the subproject as described below:
+
+### Major Version Deprecations Required
+
+__Modules__
+`iceberg-api`
+
+The API subproject is the main interface for developers and users of the Iceberg API and therefore has the strongest
+guarantees.
+Evolution of the interfaces in this subproject are enforced by [Revapi](https://revapi.org/) and require
+explicit acknowledgement of API changes.
+All public interfaces and classes require one major version for deprecation cycle.
+Any backward incompatible changes should be annotated as `@Deprecated` and removed for the next major release.
+Backward compatible changes are allowed within major versions.
+
+### Minor Version Deprecations Required
+
+__Modules__
+`iceberg-common`
+`iceberg-core`
+`iceberg-data`
+`iceberg-orc`
+`iceberg-parquet`
+
+Changes to public interfaces and classes in the subprojects listed above require a deprecation cycle of one minor
+release.
+These projects contain common and internal code used by other projects and can evolve within a major release.
+Minor release deprecation will provide other subprojects and external projects notice and opportunity to transition
+to new implementations.
+
+### Minor Version Deprecations Discretionary
+
+__modules__ (All modules not referenced above)
+
+Other modules are less likely to be extended directly and modifications should make a good faith effort to follow a
+minor version deprecation cycle.
+If there are significant structural or design changes that result in deprecations
+being difficult to orchestrate, it is up to the committers to decide if deprecation is necessary.
+
+## Deprecation Notices
+
+All interfaces, classes, and methods targeted for deprecation must include the following:
+
+1. `@Deprecated` annotation on the appropriate element
+2. `@depreceted` javadoc comment including: the version for removal, the appropriate alternative for usage
+3. Replacement of existing code paths that use the deprecated behavior
+
+Example:
+
+```java
+  /**
+   * Set the sequence number for this manifest entry.
+   *
+   * @param sequenceNumber a sequence number
+   * @deprecated since 1.0.0, will be removed in 1.1.0; use dataSequenceNumber() instead.
+   */
+  @Deprecated
+  void sequenceNumber(long sequenceNumber);
+```
+
+
 ## Iceberg Code Contribution Guidelines
 
 ### Style
@@ -198,6 +263,87 @@
 2. Use `.` to create a hierarchy of config groups
     * For example, `s3` in `s3.access-key-id`, `s3.secret-access-key`
 
+## Testing
+
+### AssertJ
+
+Prefer using [AssertJ](https://github.com/assertj/assertj) assertions as those provide a rich and intuitive set of strongly-typed assertions.
+Checks can be expressed in a fluent way and [AssertJ](https://github.com/assertj/assertj) provides rich context when assertions fail.
+Additionally, [AssertJ](https://github.com/assertj/assertj) has powerful testing capabilities on collections and exceptions.
+Please refer to the [usage guide](https://assertj.github.io/doc/#assertj-core-assertions-guide) for additional examples.
+
+```java
+// bad: will only say true != false when check fails
+assertTrue(x instanceof Xyz);
+
+// better: will show type of x when check fails
+assertThat(x).isInstanceOf(Xyz.class);
+
+// bad: will only say true != false when check fails
+assertTrue(catalog.listNamespaces().containsAll(expected));
+
+// better: will show content of expected and of catalog.listNamespaces() if check fails
+assertThat(catalog.listNamespaces()).containsAll(expected);
+```
+```java
+// ok
+assertNotNull(metadataFileLocations);
+assertEquals(metadataFileLocations.size(), 4);
+
+// better: will show the content of metadataFileLocations if check fails
+assertThat(metadataFileLocations).isNotNull().hasSize(4);
+
+// or
+assertThat(metadataFileLocations).isNotNull().hasSameSizeAs(expected).hasSize(4);
+```
+
+```java
+// bad
+try {
+    catalog.createNamespace(deniedNamespace);
+    Assert.fail("this should fail");
+} catch (Exception e) {
+    assertEquals(AccessDeniedException.class, e.getClass());
+    assertEquals("User 'testUser' has no permission to create namespace", e.getMessage());
+}
+
+// better
+assertThatThrownBy(() -> catalog.createNamespace(deniedNamespace))
+    .isInstanceOf(AccessDeniedException.class)
+    .hasMessage("User 'testUser' has no permission to create namespace");
+```
+Checks on exceptions should always make sure to assert that a particular exception message has occurred.
+
+
+### Awaitility
+
+Avoid using `Thread.sleep()` in tests as it leads to long test durations and flaky behavior if a condition takes slightly longer than expected.
+
+```java
+deleteTablesAsync();
+Thread.sleep(3000L);
+assertThat(tables()).isEmpty();
+```
+
+A better alternative is using [Awaitility](https://github.com/awaitility/awaitility) to make sure `tables()` are eventually empty. The below example will run the check
+with a default polling interval of **100 millis**:
+
+```java
+deleteTablesAsync();
+Awaitility.await("Tables were not deleted")
+    .atMost(5, TimeUnit.SECONDS)
+    .untilAsserted(() -> assertThat(tables()).isEmpty());
+```
+
+Please refer to the [usage guide](https://github.com/awaitility/awaitility/wiki/Usage) of [Awaitility](https://github.com/awaitility/awaitility) for more usage examples.
+
+
+### JUnit4 / JUnit5
+
+Iceberg currently uses a mix of JUnit4 (`org.junit` imports) and JUnit5 (`org.junit.jupiter.api` imports) tests. To allow an easier migration to JUnit5 in the future, new test classes
+that are being added to the codebase should be written purely in JUnit5 where possible.
+
+
 ## Running Benchmarks
 Some PRs/changesets might require running benchmarks to determine whether they are affecting the baseline performance. Currently there is 
 no "push a single button to get a performance comparison" solution available, therefore one has to run JMH performance tests on their local machine and