This is an automated email from the ASF dual-hosted git repository.

danny0405 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hudi.git


The following commit(s) were added to refs/heads/master by this push:
     new c3cbf4ea6ce2 feat(agent): Add Flink multi writer contract to Hudi 
Architect (#20472)
c3cbf4ea6ce2 is described below

commit c3cbf4ea6ce2a99d52a8c55ea76b13b29e807dea
Author: fhan <[email protected]>
AuthorDate: Sat Oct 31 12:37:24 2026 +0800

    feat(agent): Add Flink multi writer contract to Hudi Architect (#20472)
    
    * feat(agent): Add Flink multi writer contract to Hudi Architect
    
    * feat(agent): Harden Flink multi-writer contract evidence reconciliation
    
    * feat(agent): Fix Mypy type conflict in Flink evidence validation
    
    ---------
    
    Co-authored-by: fhan <[email protected]>
---
 .github/workflows/agent_gateway_ci.yml             |   2 +
 .../TestFlinkArchitectWriterContractFixtures.java  | 114 +++
 .../tests/test_hudi_architect_skill.py             | 791 ++++++++++++++++++++-
 hudi-ai-operator/skills/hudi-architect/README.md   |  16 +-
 hudi-ai-operator/skills/hudi-architect/RUNBOOK.md  |  14 +-
 hudi-ai-operator/skills/hudi-architect/SKILL.md    |  21 +-
 .../skills/hudi-architect/flink_writer_contract.py | 767 ++++++++++++++++++++
 .../flink-1.20-hudi-1.2.0-capabilities.md          |  29 +-
 .../flink-1.20-hudi-1.2.0-capabilities.toml        | 110 ++-
 .../references/flink-config-templates.md           |  13 +-
 .../references/flink-decision-overrides.md         |  29 +-
 .../flink-existing-table-reconciliation.md         |  27 +-
 .../references/flink-question-flow.md              |  23 +-
 .../hudi-architect/references/flink-warnings.md    |  50 +-
 .../references/flink-writer-contract.md            | 105 +++
 .../hudi-architect/validate_flink_capabilities.py  |  94 ++-
 .../skills/hudi-architect/validate_flink_design.py |  27 +-
 .../validate_flink_existing_table.py               | 245 ++++++-
 18 files changed, 2419 insertions(+), 58 deletions(-)

diff --git a/.github/workflows/agent_gateway_ci.yml 
b/.github/workflows/agent_gateway_ci.yml
index 1cae2b79ea2b..768c485b4922 100644
--- a/.github/workflows/agent_gateway_ci.yml
+++ b/.github/workflows/agent_gateway_ci.yml
@@ -87,12 +87,14 @@ jobs:
         run: >-
           ruff check src tests
           ../hudi-ai-operator/skills/hudi-architect/redact_sensitive_values.py
+          ../hudi-ai-operator/skills/hudi-architect/flink_writer_contract.py
           
../hudi-ai-operator/skills/hudi-architect/validate_flink_capabilities.py
           ../hudi-ai-operator/skills/hudi-architect/validate_flink_design.py
           
../hudi-ai-operator/skills/hudi-architect/validate_flink_existing_table.py
       - name: Type-check
         run: >-
           mypy src
+          ../hudi-ai-operator/skills/hudi-architect/flink_writer_contract.py
           
../hudi-ai-operator/skills/hudi-architect/validate_flink_capabilities.py
           ../hudi-ai-operator/skills/hudi-architect/validate_flink_design.py
           
../hudi-ai-operator/skills/hudi-architect/validate_flink_existing_table.py
diff --git 
a/hudi-agent-gateway/fixtures/hudi-architect-flink-1.20/src/test/java/org/apache/hudi/agent/architect/TestFlinkArchitectWriterContractFixtures.java
 
b/hudi-agent-gateway/fixtures/hudi-architect-flink-1.20/src/test/java/org/apache/hudi/agent/architect/TestFlinkArchitectWriterContractFixtures.java
new file mode 100644
index 000000000000..63d0b2817a08
--- /dev/null
+++ 
b/hudi-agent-gateway/fixtures/hudi-architect-flink-1.20/src/test/java/org/apache/hudi/agent/architect/TestFlinkArchitectWriterContractFixtures.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hudi.agent.architect;
+
+import org.apache.hudi.common.config.TypedProperties;
+import org.apache.hudi.common.model.HoodieFailedWritesCleaningPolicy;
+import org.apache.hudi.common.model.HoodieTableType;
+import org.apache.hudi.common.model.WriteConcurrencyMode;
+import org.apache.hudi.common.model.WriteOperationType;
+import org.apache.hudi.common.table.HoodieTableConfig;
+import org.apache.hudi.config.HoodieCleanConfig;
+import org.apache.hudi.config.HoodieIndexConfig;
+import org.apache.hudi.config.HoodieWriteConfig;
+import org.apache.hudi.index.HoodieIndex;
+import org.apache.hudi.keygen.constant.KeyGeneratorOptions;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/** Source-backed facts used by the assessment-only PR7 Writer Contract. */
+class TestFlinkArchitectWriterContractFixtures {
+
+  @Test
+  void testPinnedMultiWriterModesRequireLazyFailedWritesPolicy() {
+    
assertTrue(WriteConcurrencyMode.OPTIMISTIC_CONCURRENCY_CONTROL.supportsMultiWriter());
+    
assertTrue(WriteConcurrencyMode.NON_BLOCKING_CONCURRENCY_CONTROL.supportsMultiWriter());
+
+    HoodieCleanConfig eagerCleanConfig = HoodieCleanConfig.newBuilder()
+        .withFailedWritesCleaningPolicy(HoodieFailedWritesCleaningPolicy.EAGER)
+        .build();
+    for (WriteConcurrencyMode mode : new WriteConcurrencyMode[] {
+        WriteConcurrencyMode.OPTIMISTIC_CONCURRENCY_CONTROL,
+        WriteConcurrencyMode.NON_BLOCKING_CONCURRENCY_CONTROL}) {
+      HoodieWriteConfig config = mode == 
WriteConcurrencyMode.NON_BLOCKING_CONCURRENCY_CONTROL
+          ? validNbccBuilder().withCleanConfig(eagerCleanConfig).build()
+          : HoodieWriteConfig.newBuilder()
+              .withPath("/tmp/hudi-architect-occ")
+              .withCleanConfig(eagerCleanConfig)
+              .withWriteConcurrencyMode(mode)
+              .build();
+      assertEquals(HoodieFailedWritesCleaningPolicy.LAZY, 
config.getFailedWritesCleanPolicy());
+    }
+  }
+
+  @Test
+  void testPinnedNbccRejectsCowAndNonSimpleBucketIndex() {
+    IllegalArgumentException cowFailure = assertThrows(
+        IllegalArgumentException.class,
+        () -> nbccBuilder(HoodieTableType.COPY_ON_WRITE, 
HoodieIndex.IndexType.SIMPLE)
+            .build());
+    assertTrue(cowFailure.getMessage().contains(
+        "Non-blocking concurrency control requires the MOR table with simple 
bucket index"));
+
+    IllegalArgumentException indexFailure = assertThrows(
+        IllegalArgumentException.class,
+        () -> nbccBuilder(HoodieTableType.MERGE_ON_READ, 
HoodieIndex.IndexType.FLINK_STATE)
+            .build());
+    assertTrue(indexFailure.getMessage().contains(
+        "Non-blocking concurrency control requires the MOR table with simple 
bucket index"));
+  }
+
+  @Test
+  void testPinnedWriteOperationNames() {
+    Set<String> operations = Arrays.stream(WriteOperationType.values())
+        .map(Enum::name)
+        .collect(Collectors.toSet());
+    assertTrue(operations.containsAll(Set.of(
+        "INSERT", "UPSERT", "BULK_INSERT", "DELETE", "COMPACT", "CLUSTER", 
"UNKNOWN")));
+  }
+
+  private static HoodieWriteConfig.Builder validNbccBuilder() {
+    return nbccBuilder(HoodieTableType.MERGE_ON_READ, 
HoodieIndex.IndexType.BUCKET);
+  }
+
+  private static HoodieWriteConfig.Builder nbccBuilder(
+      HoodieTableType tableType, HoodieIndex.IndexType indexType) {
+    TypedProperties properties = new TypedProperties();
+    properties.put(HoodieTableConfig.TYPE.key(), tableType.name());
+    properties.put(KeyGeneratorOptions.RECORDKEY_FIELD_NAME.key(), "id");
+    HoodieIndexConfig.Builder indexBuilder = HoodieIndexConfig.newBuilder()
+        .fromProperties(properties)
+        .withIndexType(indexType);
+    if (indexType == HoodieIndex.IndexType.BUCKET) {
+      
indexBuilder.withBucketIndexEngineType(HoodieIndex.BucketIndexEngineType.SIMPLE);
+    }
+    return HoodieWriteConfig.newBuilder()
+        .withPath("/tmp/hudi-architect-nbcc")
+        .withIndexConfig(indexBuilder.build())
+        
.withWriteConcurrencyMode(WriteConcurrencyMode.NON_BLOCKING_CONCURRENCY_CONTROL);
+  }
+}
diff --git a/hudi-agent-gateway/tests/test_hudi_architect_skill.py 
b/hudi-agent-gateway/tests/test_hudi_architect_skill.py
index 90dd4ff53b0e..3677e7aed649 100644
--- a/hudi-agent-gateway/tests/test_hudi_architect_skill.py
+++ b/hudi-agent-gateway/tests/test_hudi_architect_skill.py
@@ -62,6 +62,9 @@ PR2_JAVA_FIXTURE = (
     / "TestFlinkArchitectSqlFixtures.java"
 )
 PR6_JAVA_FIXTURE = 
PR2_JAVA_FIXTURE.with_name("TestFlinkArchitectCatalogFixtures.java")
+PR7_JAVA_FIXTURE = PR2_JAVA_FIXTURE.with_name(
+    "TestFlinkArchitectWriterContractFixtures.java"
+)
 
 FLINK_REFERENCES = (
     "flink-1.20-hudi-1.2.0-capabilities.md",
@@ -72,6 +75,7 @@ FLINK_REFERENCES = (
     "flink-config-templates.md",
     "flink-existing-table-reconciliation.md",
     "flink-catalog-composition.md",
+    "flink-writer-contract.md",
 )
 
 
@@ -340,6 +344,45 @@ def _compatible_existing_envelope(design: dict) -> dict:
     }
 
 
+def _pr7_writer_contract(
+    concurrency_mode: str = "OPTIMISTIC_CONCURRENCY_CONTROL",
+) -> dict:
+    return {
+        "contract_schema": 1,
+        "primary_writer_id": "flink-orders-stream",
+        "additional_writers": [
+            {
+                "writer_id": "spark-orders-backfill",
+                "role": "BACKFILL",
+                "relationship_to_primary": "INDEPENDENT_CONCURRENT",
+                "engine": "SPARK",
+                "engine_version": "3.5",
+                "hudi_version": "1.2.0",
+                "operation": "BULK_INSERT",
+            }
+        ],
+        "ownership": {
+            "schema_evolution_owner_id": "flink-orders-stream",
+            "table_configuration_owner_id": "flink-orders-stream",
+        },
+        "observed_concurrency": {
+            "mode": concurrency_mode,
+            "lock_provider_class": (
+                "org.apache.hudi.client.transaction.lock."
+                "FileSystemBasedLockProvider"
+            ),
+            "lock_configuration_consistency": "CONSISTENT",
+        },
+    }
+
+
+def _pr7_design(fixture: Path, concurrency_mode: str) -> dict:
+    design = json.loads(_read(fixture))
+    design["safety"]["writer_model"] = "multi_writer"
+    design["writer_contract"] = _pr7_writer_contract(concurrency_mode)
+    return design
+
+
 def test_flink_references_exist_and_are_linked_from_skill() -> None:
     skill = _read(SKILL_DIR / "SKILL.md")
     for filename in FLINK_REFERENCES:
@@ -380,7 +423,7 @@ def 
test_flink_baseline_has_bounded_append_and_mutable_contracts() -> None:
         "fixture_version": "1.20.1",
     }
     assert PINNED_HUDI_REVISION in capability
-    assert manifest["schema_version"] == 6
+    assert manifest["schema_version"] == 7
     assert manifest["fixture_artifacts"] == {
         "hudi_bundle": "org.apache.hudi:hudi-flink1.20-bundle:1.2.0",
         "flink_version": "1.20.1",
@@ -444,6 +487,16 @@ def 
test_flink_baseline_has_bounded_append_and_mutable_contracts() -> None:
     assert catalog_contract["contract_schema"] == 1
     assert catalog_contract["supported_design_contract_schemas"] == [1, 2, 3]
     assert catalog_contract["catalog_freshness"] == "post_commit_eventual"
+    writer_contract = manifest["multi_writer_contract"]
+    assert writer_contract["contract_schema"] == 1
+    assert writer_contract["supported_design_contract_schemas"] == [1, 2, 3]
+    assert writer_contract["assessment_only"] is True
+    assert writer_contract["executable_sql"] is False
+    assert writer_contract["capability_results"] == [
+        "NOT_APPLICABLE",
+        "UNVERIFIED",
+        "UNSUPPORTED",
+    ]
     assert [profile["id"] for profile in manifest["catalog_profiles"]] == [
         "direct_sql_none",
         "direct_sql_hive_metastore",
@@ -471,7 +524,7 @@ def 
test_flink_capability_validation_emits_pinned_evidence() -> None:
     assert result.returncode == 0, result.stderr
     assert json.loads(result.stdout) == {
         "baseline_id": "hudi-1.2.0-flink-1.20",
-        "capability_manifest_schema": 6,
+        "capability_manifest_schema": 7,
         "catalog_contract_schema": 1,
         "catalog_profile": "direct_sql_none",
         "design_contract_schema": 1,
@@ -485,6 +538,7 @@ def 
test_flink_capability_validation_emits_pinned_evidence() -> None:
             "hudi_hms_catalog_hive_metastore",
         ],
         "supported_design_contract_schemas": [1, 2, 3],
+        "writer_contract_schema": 1,
     }
 
 
@@ -575,7 +629,11 @@ def 
test_flink_capability_manifest_records_executable_acceptance_contracts() ->
         for node in ast.walk(ast.parse(_read(Path(__file__))))
         if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
     }
-    java_fixture = _read(PR2_JAVA_FIXTURE) + _read(PR6_JAVA_FIXTURE)
+    java_fixture = (
+        _read(PR2_JAVA_FIXTURE)
+        + _read(PR6_JAVA_FIXTURE)
+        + _read(PR7_JAVA_FIXTURE)
+    )
     effective_settings = {
         setting["applies_to"]: {
             candidate["key"]: candidate["value"]
@@ -720,6 +778,14 @@ def 
test_flink_capability_manifest_records_executable_acceptance_contracts() ->
             "test_pr4_valid_mutable_mor_design_renders_exact_executable_sql",
             "testMutableMorSinkAndCompactionPlan",
         ),
+        "FLINK_MULTI_WRITER_REVIEW": (
+            "test_pr7_complete_occ_contract_is_assessment_only",
+            "testPinnedMultiWriterModesRequireLazyFailedWritesPolicy",
+        ),
+        "FLINK_WRITER_COMBINATION_UNSUPPORTED": (
+            "test_pr7_nbcc_is_unsupported_for_existing_bounded_indexes",
+            "testPinnedNbccRejectsCowAndNonSimpleBucketIndex",
+        ),
     }
     for check in manifest["implemented_acceptance_checks"]:
         assert check["python_test"] in python_tests
@@ -831,7 +897,7 @@ def 
test_pr2_valid_designs_render_exact_executable_sql(fixture_name: str) -> Non
     ).rstrip("\n")
     assert assessment["validation_evidence"] == {
         "baseline_id": "hudi-1.2.0-flink-1.20",
-        "capability_manifest_schema": 6,
+        "capability_manifest_schema": 7,
         "catalog_contract_schema": 1,
         "catalog_profile": "direct_sql_none",
         "design_contract_schema": 1,
@@ -845,6 +911,7 @@ def 
test_pr2_valid_designs_render_exact_executable_sql(fixture_name: str) -> Non
             "hudi_hms_catalog_hive_metastore",
         ],
         "supported_design_contract_schemas": [1, 2, 3],
+        "writer_contract_schema": 1,
     }
 
 
@@ -861,7 +928,7 @@ def 
test_pr3_valid_mutable_design_renders_exact_executable_sql() -> None:
         PR3_FIXTURE_DIR / "mutable_cow.sql"
     ).rstrip("\n")
     assert assessment["validation_evidence"]["design_contract_schema"] == 2
-    assert assessment["validation_evidence"]["capability_manifest_schema"] == 6
+    assert assessment["validation_evidence"]["capability_manifest_schema"] == 7
 
 
 def test_pr4_valid_mutable_mor_design_renders_exact_executable_sql() -> None:
@@ -878,7 +945,7 @@ def 
test_pr4_valid_mutable_mor_design_renders_exact_executable_sql() -> None:
     ).rstrip("\n")
     assert assessment["validation_evidence"] == {
         "baseline_id": "hudi-1.2.0-flink-1.20",
-        "capability_manifest_schema": 6,
+        "capability_manifest_schema": 7,
         "catalog_contract_schema": 1,
         "catalog_profile": "direct_sql_none",
         "design_contract_schema": 3,
@@ -892,6 +959,7 @@ def 
test_pr4_valid_mutable_mor_design_renders_exact_executable_sql() -> None:
             "hudi_hms_catalog_hive_metastore",
         ],
         "supported_design_contract_schemas": [1, 2, 3],
+        "writer_contract_schema": 1,
     }
 
 
@@ -1496,6 +1564,715 @@ def 
test_pr6_existing_catalog_mutation_remains_unverified(tmp_path: Path) -> Non
     assert "verify_hms_connectivity" in assessment["catalog_deployment_checks"]
 
 
+def test_pr7_keeps_single_writer_contract_not_applicable_and_sql_stable() -> 
None:
+    result = _run_flink_design_validator(PR2_FIXTURE_DIR / "stable_key.json")
+
+    assert result.returncode == 0, result.stderr
+    assessment = json.loads(result.stdout)
+    assert assessment["writer_contract_result"] == "NOT_APPLICABLE"
+    assert assessment["multi_writer_capability"] == "NOT_APPLICABLE"
+    assert assessment["normalized_writer_contract"] == {}
+    assert assessment["writer_capability_notes"] == []
+    assert assessment["writer_deployment_checks"] == []
+    assert assessment["artifacts"]["combined_sql"] == _read_sql_golden(
+        PR2_FIXTURE_DIR / "stable_key.sql"
+    ).rstrip("\n")
+
+
+def test_pr7_requires_contract_for_confirmed_multi_writer(tmp_path: Path) -> 
None:
+    design = json.loads(_read(PR2_FIXTURE_DIR / "stable_key.json"))
+    design["safety"]["writer_model"] = "multi_writer"
+
+    result = _run_flink_design_validator(_write_design(tmp_path, design))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["status"] == "REVIEW_REQUIRED"
+    assert assessment["writer_contract_result"] == "INCOMPLETE"
+    assert assessment["multi_writer_capability"] == "UNVERIFIED"
+    assert "FLINK_MULTI_WRITER_REVIEW" in assessment["finding_codes"]
+    assert "FLINK_WRITER_CONTRACT_REQUIRED" in assessment["finding_codes"]
+    assert "artifacts" not in assessment
+
+
+def test_pr7_complete_occ_contract_is_assessment_only(tmp_path: Path) -> None:
+    design = _pr7_design(
+        PR2_FIXTURE_DIR / "stable_key.json",
+        "OPTIMISTIC_CONCURRENCY_CONTROL",
+    )
+
+    result = _run_flink_design_validator(_write_design(tmp_path, design))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["status"] == "REVIEW_REQUIRED"
+    assert assessment["writer_contract_result"] == "COMPLETE"
+    assert assessment["multi_writer_capability"] == "UNVERIFIED"
+    assert assessment["normalized_writer_contract"]["primary_writer_facts"] == 
{
+        "engine": "FLINK",
+        "engine_version": "1.20.1",
+        "hudi_version": "1.2.0",
+        "operation": "INSERT",
+    }
+    assert [note["code"] for note in assessment["writer_capability_notes"]] == 
[
+        "FLINK_OCC_DEPLOYMENT_UNVERIFIED",
+        "FLINK_CROSS_ENGINE_WRITER_UNVERIFIED",
+    ]
+    assert len(assessment["writer_deployment_checks"]) == 6
+    assert assessment["finding_codes"] == ["FLINK_MULTI_WRITER_REVIEW"]
+    assert assessment["executable_eligible"] is False
+    assert "artifacts" not in assessment
+
+
+def test_pr7_additional_flink_writer_version_drift_is_unverified(
+    tmp_path: Path,
+) -> None:
+    design = _pr7_design(
+        PR2_FIXTURE_DIR / "stable_key.json",
+        "OPTIMISTIC_CONCURRENCY_CONTROL",
+    )
+    additional_writer = design["writer_contract"]["additional_writers"][0]
+    additional_writer["engine"] = "FLINK"
+    additional_writer["engine_version"] = "1.19.1"
+
+    result = _run_flink_design_validator(_write_design(tmp_path, design))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["status"] == "REVIEW_REQUIRED"
+    assert assessment["writer_contract_result"] == "COMPLETE"
+    assert "FLINK_VERSION_UNVERIFIED" in assessment["finding_codes"]
+    assert "FLINK_CROSS_ENGINE_WRITER_UNVERIFIED" not in {
+        note["code"] for note in assessment["writer_capability_notes"]
+    }
+    assert assessment["executable_eligible"] is False
+
+
+def test_pr7_invalid_writer_fact_messages_are_deterministic(tmp_path: Path) -> 
None:
+    design = _pr7_design(
+        PR2_FIXTURE_DIR / "stable_key.json",
+        "OPTIMISTIC_CONCURRENCY_CONTROL",
+    )
+    design["writer_contract"]["additional_writers"] = [{}]
+
+    result = _run_flink_design_validator(_write_design(tmp_path, design))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    invalid = next(
+        finding
+        for finding in assessment["findings"]
+        if finding["code"] == "FLINK_WRITER_CONTRACT_INVALID"
+    )
+    assert invalid["message"] == (
+        "writer_contract.additional_writers[0].engine must be a non-empty 
string; "
+        "writer_contract.additional_writers[0].engine_version must be a 
non-empty string; "
+        "writer_contract.additional_writers[0].hudi_version must be a 
non-empty string; "
+        "writer_contract.additional_writers[0].operation must be a non-empty 
string; "
+        "writer_contract.additional_writers[0].relationship_to_primary must be 
a non-empty "
+        "string; writer_contract.additional_writers[0].role must be a 
non-empty string; "
+        "writer_contract.additional_writers[0].writer_id must be a non-empty 
string"
+    )
+
+
[email protected](
+    "fixture",
+    [
+        PR2_FIXTURE_DIR / "stable_key.json",
+        PR4_FIXTURE_DIR / "mutable_mor.json",
+    ],
+)
+def test_pr7_nbcc_is_unsupported_for_existing_bounded_indexes(
+    tmp_path: Path, fixture: Path
+) -> None:
+    design = _pr7_design(fixture, "NON_BLOCKING_CONCURRENCY_CONTROL")
+
+    result = _run_flink_design_validator(_write_design(tmp_path, design))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["status"] == "BLOCKED"
+    assert assessment["writer_contract_result"] == "COMPLETE"
+    assert assessment["multi_writer_capability"] == "UNSUPPORTED"
+    assert "FLINK_WRITER_COMBINATION_UNSUPPORTED" in 
assessment["finding_codes"]
+    assert assessment["executable_eligible"] is False
+    assert "artifacts" not in assessment
+
+
[email protected](
+    "contract_patch",
+    [
+        {"contract_schema": True},
+        {"additional_writers": []},
+        {"unsupported_field": "value"},
+    ],
+)
+def test_pr7_malformed_writer_contract_fails_closed(
+    tmp_path: Path, contract_patch: dict
+) -> None:
+    design = _pr7_design(
+        PR2_FIXTURE_DIR / "stable_key.json",
+        "OPTIMISTIC_CONCURRENCY_CONTROL",
+    )
+    design["writer_contract"].update(contract_patch)
+
+    result = _run_flink_design_validator(_write_design(tmp_path, design))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["writer_contract_result"] == "INCOMPLETE"
+    assert assessment["multi_writer_capability"] == "UNVERIFIED"
+    assert assessment["normalized_writer_contract"] == {}
+    assert "FLINK_WRITER_CONTRACT_INVALID" in assessment["finding_codes"]
+    assert assessment["executable_eligible"] is False
+    assert "artifacts" not in assessment
+
+
+def test_pr7_unknown_writer_model_does_not_fabricate_contract(
+    tmp_path: Path,
+) -> None:
+    design = json.loads(_read(PR2_FIXTURE_DIR / "stable_key.json"))
+    design["safety"]["writer_model"] = "unknown"
+
+    result = _run_flink_design_validator(_write_design(tmp_path, design))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["status"] == "REVIEW_REQUIRED"
+    assert assessment["writer_contract_result"] == "NOT_APPLICABLE"
+    assert assessment["multi_writer_capability"] == "NOT_APPLICABLE"
+    assert assessment["normalized_writer_contract"] == {}
+    assert assessment["writer_deployment_checks"] == []
+    assert assessment["finding_codes"] == ["FLINK_WRITER_MODEL_UNRESOLVED"]
+    assert "artifacts" not in assessment
+
+
[email protected]("writer_model", [[], {}])
+def test_pr7_non_string_writer_model_fails_closed(
+    tmp_path: Path, writer_model: object
+) -> None:
+    design = json.loads(_read(PR2_FIXTURE_DIR / "stable_key.json"))
+    design["safety"]["writer_model"] = writer_model
+
+    result = _run_flink_design_validator(_write_design(tmp_path, design))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["status"] == "REVIEW_REQUIRED"
+    assert assessment["writer_contract_result"] == "NOT_APPLICABLE"
+    assert assessment["multi_writer_capability"] == "NOT_APPLICABLE"
+    assert assessment["finding_codes"] == ["FLINK_WRITER_MODEL_UNRESOLVED"]
+    assert "artifacts" not in assessment
+
+
[email protected]("writer_model", [[], {}])
+def test_pr7_existing_table_rejects_non_string_writer_model_without_traceback(
+    tmp_path: Path, writer_model: object
+) -> None:
+    design = json.loads(_read(PR2_FIXTURE_DIR / "stable_key.json"))
+    envelope = _compatible_existing_envelope(design)
+    envelope["desired_contract"]["safety"]["writer_model"] = writer_model
+
+    result = _run_flink_existing_table_validator(_write_design(tmp_path, 
envelope))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["status"] == "REVIEW_REQUIRED"
+    assert assessment["reconciliation_result"] == "UNVERIFIED"
+    assert assessment["change_disposition"] == "UNVERIFIED"
+    assert assessment["writer_contract_result"] == "NOT_APPLICABLE"
+    assert "FLINK_WRITER_MODEL_UNRESOLVED" in assessment["finding_codes"]
+    assert assessment["executable_eligible"] is False
+
+
+def test_pr7_single_writer_inventory_disagreement_stays_unverified(
+    tmp_path: Path,
+) -> None:
+    design = json.loads(_read(PR3_FIXTURE_DIR / "mutable_cow.json"))
+    envelope = _compatible_existing_envelope(design)
+    inventory = next(
+        item for item in envelope["evidence"] if item["kind"] == 
"writer_inventory"
+    )
+    inventory["facts"]["active_writers"] = ["writer1", "writer2"]
+
+    result = _run_flink_existing_table_validator(_write_design(tmp_path, 
envelope))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["status"] == "REVIEW_REQUIRED"
+    assert assessment["reconciliation_result"] == "UNVERIFIED"
+    assert assessment["change_disposition"] == "UNVERIFIED"
+    assert assessment["writer_contract_result"] == "NOT_APPLICABLE"
+    assert assessment["multi_writer_capability"] == "NOT_APPLICABLE"
+    assert "FLINK_MULTI_WRITER_REVIEW" in assessment["finding_codes"]
+    assert "FLINK_EXISTING_EVIDENCE_CONFLICT" in assessment["finding_codes"]
+    comparison = next(
+        item
+        for item in assessment["comparisons"]
+        if item["field"] == "writer_inventory.active_writers"
+    )
+    assert comparison == {
+        "desired": {"maximum_count": 1},
+        "field": "writer_inventory.active_writers",
+        "observed": ["writer1", "writer2"],
+        "result": "UNVERIFIED",
+        "source_ids": ["writers-1"],
+    }
+    assert assessment["executable_eligible"] is False
+
+
+def test_pr7_separate_inventory_snapshots_do_not_fabricate_internal_conflict(
+    tmp_path: Path,
+) -> None:
+    design = json.loads(_read(PR3_FIXTURE_DIR / "mutable_cow.json"))
+    envelope = _compatible_existing_envelope(design)
+    inventory = next(
+        item for item in envelope["evidence"] if item["kind"] == 
"writer_inventory"
+    )
+    second_inventory = copy.deepcopy(inventory)
+    second_inventory["evidence_id"] = "writers-2"
+    second_inventory["sha256"] = "8" * 64
+    second_inventory["facts"] = {"active_writers": ["writer1", "writer2"]}
+    envelope["evidence"].append(second_inventory)
+
+    result = _run_flink_existing_table_validator(_write_design(tmp_path, 
envelope))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["status"] == "REVIEW_REQUIRED"
+    assert assessment["reconciliation_result"] == "UNVERIFIED"
+    assert "FLINK_MULTI_WRITER_REVIEW" in assessment["finding_codes"]
+    assert "FLINK_EXISTING_EVIDENCE_CONFLICT" not in 
assessment["finding_codes"]
+
+
[email protected](
+    ("concurrency_mode", "expected_status", "expected_capability"),
+    [
+        ("OPTIMISTIC_CONCURRENCY_CONTROL", "REVIEW_REQUIRED", "UNVERIFIED"),
+        ("NON_BLOCKING_CONCURRENCY_CONTROL", "BLOCKED", "UNSUPPORTED"),
+    ],
+)
+def test_pr7_single_writer_concurrency_evidence_prevents_compatibility(
+    tmp_path: Path,
+    concurrency_mode: str,
+    expected_status: str,
+    expected_capability: str,
+) -> None:
+    design = json.loads(_read(PR3_FIXTURE_DIR / "mutable_cow.json"))
+    envelope = _compatible_existing_envelope(design)
+    writer_config = next(
+        item for item in envelope["evidence"] if item["kind"] == 
"flink_writer_config"
+    )
+    writer_config["facts"].update(
+        {
+            "write_concurrency_mode": concurrency_mode,
+            "lock_configuration_consistency": "CONFLICTING",
+        }
+    )
+
+    result = _run_flink_existing_table_validator(_write_design(tmp_path, 
envelope))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["status"] == expected_status
+    assert assessment["reconciliation_result"] == "UNVERIFIED"
+    assert assessment["change_disposition"] == "UNVERIFIED"
+    assert assessment["writer_contract_result"] == "NOT_APPLICABLE"
+    assert assessment["multi_writer_capability"] == expected_capability
+    assert "FLINK_MULTI_WRITER_REVIEW" in assessment["finding_codes"]
+    assert "FLINK_EXISTING_EVIDENCE_CONFLICT" in assessment["finding_codes"]
+    if concurrency_mode == "NON_BLOCKING_CONCURRENCY_CONTROL":
+        assert "FLINK_WRITER_COMBINATION_UNSUPPORTED" in 
assessment["finding_codes"]
+    writer_fields = {
+        item["field"]: item for item in assessment["comparisons"]
+    }
+    assert writer_fields["writer_config.write_concurrency_mode"]["observed"] 
== (
+        concurrency_mode
+    )
+    assert writer_fields["writer_config.lock_configuration_consistency"][
+        "observed"
+    ] == "CONFLICTING"
+    assert assessment["executable_eligible"] is False
+
+
[email protected](
+    ("fact_name", "invalid_value"),
+    [
+        ("write_concurrency_mode", "MAGIC_CONCURRENCY"),
+        ("lock_configuration_consistency", "PARTIALLY_CONSISTENT"),
+    ],
+)
+def test_pr7_rejects_unbounded_concurrency_evidence_enums(
+    tmp_path: Path, fact_name: str, invalid_value: str
+) -> None:
+    design = json.loads(_read(PR3_FIXTURE_DIR / "mutable_cow.json"))
+    envelope = _compatible_existing_envelope(design)
+    writer_config = next(
+        item for item in envelope["evidence"] if item["kind"] == 
"flink_writer_config"
+    )
+    writer_config["facts"][fact_name] = invalid_value
+
+    result = _run_flink_existing_table_validator(_write_design(tmp_path, 
envelope))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["status"] == "INCOMPLETE"
+    assert assessment["reconciliation_result"] == "UNVERIFIED"
+    assert assessment["change_disposition"] == "UNVERIFIED"
+    assert "FLINK_EXISTING_EVIDENCE_INVALID" in assessment["finding_codes"]
+    assert not any(
+        item["field"] == f"writer_config.{fact_name}"
+        for item in assessment["comparisons"]
+    )
+
+
+def test_pr7_existing_table_matches_optional_writer_facts(tmp_path: Path) -> 
None:
+    design = _pr7_design(
+        PR3_FIXTURE_DIR / "mutable_cow.json",
+        "OPTIMISTIC_CONCURRENCY_CONTROL",
+    )
+    envelope = _compatible_existing_envelope(design)
+    inventory = next(
+        item for item in envelope["evidence"] if item["kind"] == 
"writer_inventory"
+    )
+    inventory["facts"].update(
+        {
+            "active_writers": ["spark-orders-backfill", "flink-orders-stream"],
+            "concurrent_writers": True,
+            "schema_evolution_owner_id": "flink-orders-stream",
+        }
+    )
+    writer_config = next(
+        item for item in envelope["evidence"] if item["kind"] == 
"flink_writer_config"
+    )
+    writer_config["facts"].update(
+        {
+            "write_concurrency_mode": "OPTIMISTIC_CONCURRENCY_CONTROL",
+            "lock_provider_class": (
+                "org.apache.hudi.client.transaction.lock."
+                "FileSystemBasedLockProvider"
+            ),
+            "lock_configuration_consistency": "CONSISTENT",
+        }
+    )
+
+    result = _run_flink_existing_table_validator(_write_design(tmp_path, 
envelope))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["status"] == "REVIEW_REQUIRED"
+    assert assessment["reconciliation_result"] == "UNVERIFIED"
+    assert assessment["writer_contract_result"] == "COMPLETE"
+    assert assessment["multi_writer_capability"] == "UNVERIFIED"
+    assert "FLINK_WRITER_CONTRACT_CONFLICT" not in assessment["finding_codes"]
+    writer_fields = {
+        item["field"]: item["result"]
+        for item in assessment["comparisons"]
+        if item["field"].startswith(("writer_inventory.", "writer_config."))
+    }
+    assert writer_fields["writer_inventory.active_writers"] == "MATCH"
+    assert writer_fields["writer_inventory.schema_evolution_owner_id"] == 
"MATCH"
+    assert writer_fields["writer_config.write_concurrency_mode"] == "MATCH"
+    assert writer_fields["writer_config.lock_provider_class"] == "MATCH"
+    assert writer_fields["writer_config.lock_configuration_consistency"] == 
"MATCH"
+    assert "confirm_single_writer" not in {
+        item["check"] for item in assessment["deployment_checks"]
+    }
+    assert "verify_concurrent_writer_inventory" in {
+        item["check"] for item in assessment["writer_deployment_checks"]
+    }
+
+
+def test_pr7_inactive_declared_writer_does_not_conflict_with_active_snapshot(
+    tmp_path: Path,
+) -> None:
+    design = _pr7_design(
+        PR3_FIXTURE_DIR / "mutable_cow.json",
+        "OPTIMISTIC_CONCURRENCY_CONTROL",
+    )
+    envelope = _compatible_existing_envelope(design)
+    inventory = next(
+        item for item in envelope["evidence"] if item["kind"] == 
"writer_inventory"
+    )
+    inventory["facts"]["active_writers"] = ["flink-orders-stream"]
+
+    result = _run_flink_existing_table_validator(_write_design(tmp_path, 
envelope))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["status"] == "REVIEW_REQUIRED"
+    assert assessment["writer_contract_result"] == "COMPLETE"
+    assert "FLINK_WRITER_CONTRACT_CONFLICT" not in assessment["finding_codes"]
+    comparison = next(
+        item
+        for item in assessment["comparisons"]
+        if item["field"] == "writer_inventory.active_writers"
+    )
+    assert comparison == {
+        "desired": {
+            "allowed_writer_ids": [
+                "flink-orders-stream",
+                "spark-orders-backfill",
+            ]
+        },
+        "field": "writer_inventory.active_writers",
+        "observed": ["flink-orders-stream"],
+        "result": "MATCH",
+        "source_ids": ["writers-1"],
+    }
+    assert assessment["reconciliation_result"] == "UNVERIFIED"
+    assert assessment["change_disposition"] == "UNVERIFIED"
+
+
+def test_pr7_undeclared_active_writer_conflicts_with_declared_topology(
+    tmp_path: Path,
+) -> None:
+    design = _pr7_design(
+        PR3_FIXTURE_DIR / "mutable_cow.json",
+        "OPTIMISTIC_CONCURRENCY_CONTROL",
+    )
+    envelope = _compatible_existing_envelope(design)
+    inventory = next(
+        item for item in envelope["evidence"] if item["kind"] == 
"writer_inventory"
+    )
+    inventory["facts"].update(
+        {
+            "active_writers": ["flink-orders-stream", "undeclared-writer"],
+            "concurrent_writers": True,
+        }
+    )
+
+    result = _run_flink_existing_table_validator(_write_design(tmp_path, 
envelope))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["writer_contract_result"] == "CONFLICT"
+    assert "FLINK_WRITER_CONTRACT_CONFLICT" in assessment["finding_codes"]
+    comparison = next(
+        item
+        for item in assessment["comparisons"]
+        if item["field"] == "writer_inventory.active_writers"
+    )
+    assert comparison["result"] == "UNVERIFIED"
+    assert assessment["reconciliation_result"] == "UNVERIFIED"
+    assert assessment["change_disposition"] == "UNVERIFIED"
+
+
[email protected](
+    ("fact_name", "fact_value", "expected_contract_result", 
"expected_capability"),
+    [
+        (
+            "write_concurrency_mode",
+            "NON_BLOCKING_CONCURRENCY_CONTROL",
+            "COMPLETE",
+            "UNSUPPORTED",
+        ),
+        (
+            "lock_configuration_consistency",
+            "CONFLICTING",
+            "CONFLICT",
+            "UNVERIFIED",
+        ),
+    ],
+)
+def test_pr7_assesses_concurrency_evidence_without_optional_contract_block(
+    tmp_path: Path,
+    fact_name: str,
+    fact_value: str,
+    expected_contract_result: str,
+    expected_capability: str,
+) -> None:
+    design = _pr7_design(
+        PR3_FIXTURE_DIR / "mutable_cow.json",
+        "OPTIMISTIC_CONCURRENCY_CONTROL",
+    )
+    del design["writer_contract"]["observed_concurrency"]
+    envelope = _compatible_existing_envelope(design)
+    inventory = next(
+        item for item in envelope["evidence"] if item["kind"] == 
"writer_inventory"
+    )
+    inventory["facts"]["concurrent_writers"] = True
+    writer_config = next(
+        item for item in envelope["evidence"] if item["kind"] == 
"flink_writer_config"
+    )
+    writer_config["facts"][fact_name] = fact_value
+
+    result = _run_flink_existing_table_validator(_write_design(tmp_path, 
envelope))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["writer_contract_result"] == expected_contract_result
+    assert assessment["multi_writer_capability"] == expected_capability
+    expected_finding = (
+        "FLINK_WRITER_COMBINATION_UNSUPPORTED"
+        if fact_name == "write_concurrency_mode"
+        else "FLINK_WRITER_CONTRACT_CONFLICT"
+    )
+    assert expected_finding in assessment["finding_codes"]
+    assert "observed_concurrency" not in 
assessment["normalized_writer_contract"]
+    comparison = next(
+        item
+        for item in assessment["comparisons"]
+        if item["field"] == f"writer_config.{fact_name}"
+    )
+    assert comparison == {
+        "desired": None,
+        "field": f"writer_config.{fact_name}",
+        "observed": fact_value,
+        "result": "UNVERIFIED",
+        "source_ids": ["writer-config-1"],
+    }
+    assert assessment["reconciliation_result"] == "UNVERIFIED"
+    assert assessment["change_disposition"] == "UNVERIFIED"
+    assert assessment["executable_eligible"] is False
+
+
[email protected]("contract_state", ["missing", "invalid"])
+def test_pr7_assesses_nbcc_evidence_even_when_contract_is_incomplete(
+    tmp_path: Path, contract_state: str
+) -> None:
+    design = json.loads(_read(PR3_FIXTURE_DIR / "mutable_cow.json"))
+    design["safety"]["writer_model"] = "multi_writer"
+    if contract_state == "invalid":
+        design["writer_contract"] = []
+    envelope = _compatible_existing_envelope(design)
+    inventory = next(
+        item for item in envelope["evidence"] if item["kind"] == 
"writer_inventory"
+    )
+    inventory["facts"]["concurrent_writers"] = True
+    writer_config = next(
+        item for item in envelope["evidence"] if item["kind"] == 
"flink_writer_config"
+    )
+    writer_config["facts"]["write_concurrency_mode"] = (
+        "NON_BLOCKING_CONCURRENCY_CONTROL"
+    )
+
+    result = _run_flink_existing_table_validator(_write_design(tmp_path, 
envelope))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["status"] == "BLOCKED"
+    assert assessment["writer_contract_result"] == "INCOMPLETE"
+    assert assessment["multi_writer_capability"] == "UNSUPPORTED"
+    expected_contract_finding = (
+        "FLINK_WRITER_CONTRACT_REQUIRED"
+        if contract_state == "missing"
+        else "FLINK_WRITER_CONTRACT_INVALID"
+    )
+    assert expected_contract_finding in assessment["finding_codes"]
+    assert "FLINK_WRITER_COMBINATION_UNSUPPORTED" in 
assessment["finding_codes"]
+    assert assessment["normalized_writer_contract"] == {}
+    assert assessment["reconciliation_result"] == "UNVERIFIED"
+    assert assessment["change_disposition"] == "UNVERIFIED"
+    assert assessment["executable_eligible"] is False
+
+
+def test_pr7_conflicting_lock_evidence_does_not_reclassify_missing_contract(
+    tmp_path: Path,
+) -> None:
+    design = json.loads(_read(PR3_FIXTURE_DIR / "mutable_cow.json"))
+    design["safety"]["writer_model"] = "multi_writer"
+    envelope = _compatible_existing_envelope(design)
+    writer_config = next(
+        item for item in envelope["evidence"] if item["kind"] == 
"flink_writer_config"
+    )
+    writer_config["facts"]["lock_configuration_consistency"] = "CONFLICTING"
+
+    result = _run_flink_existing_table_validator(_write_design(tmp_path, 
envelope))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["writer_contract_result"] == "INCOMPLETE"
+    assert assessment["multi_writer_capability"] == "UNVERIFIED"
+    assert "FLINK_WRITER_CONTRACT_REQUIRED" in assessment["finding_codes"]
+    assert "FLINK_EXISTING_EVIDENCE_CONFLICT" in assessment["finding_codes"]
+    assert "FLINK_WRITER_CONTRACT_CONFLICT" not in assessment["finding_codes"]
+
+
+def test_pr7_evidence_nbcc_overrides_explicit_occ_capability_conservatively(
+    tmp_path: Path,
+) -> None:
+    design = _pr7_design(
+        PR3_FIXTURE_DIR / "mutable_cow.json",
+        "OPTIMISTIC_CONCURRENCY_CONTROL",
+    )
+    envelope = _compatible_existing_envelope(design)
+    inventory = next(
+        item for item in envelope["evidence"] if item["kind"] == 
"writer_inventory"
+    )
+    inventory["facts"]["concurrent_writers"] = True
+    writer_config = next(
+        item for item in envelope["evidence"] if item["kind"] == 
"flink_writer_config"
+    )
+    writer_config["facts"]["write_concurrency_mode"] = (
+        "NON_BLOCKING_CONCURRENCY_CONTROL"
+    )
+
+    result = _run_flink_existing_table_validator(_write_design(tmp_path, 
envelope))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["writer_contract_result"] == "CONFLICT"
+    assert assessment["multi_writer_capability"] == "UNSUPPORTED"
+    assert "FLINK_WRITER_CONTRACT_CONFLICT" in assessment["finding_codes"]
+    assert "FLINK_WRITER_COMBINATION_UNSUPPORTED" in 
assessment["finding_codes"]
+    assert assessment["reconciliation_result"] == "UNVERIFIED"
+    assert assessment["change_disposition"] == "UNVERIFIED"
+    assert assessment["executable_eligible"] is False
+
+
+def test_pr7_existing_writer_evidence_conflict_stays_unverified(
+    tmp_path: Path,
+) -> None:
+    design = _pr7_design(
+        PR3_FIXTURE_DIR / "mutable_cow.json",
+        "OPTIMISTIC_CONCURRENCY_CONTROL",
+    )
+    envelope = _compatible_existing_envelope(design)
+    inventory = next(
+        item for item in envelope["evidence"] if item["kind"] == 
"writer_inventory"
+    )
+    inventory["facts"].update(
+        {
+            "active_writers": ["flink-orders-stream", "spark-orders-backfill"],
+            "concurrent_writers": True,
+            "schema_evolution_owner_id": "flink-orders-stream",
+        }
+    )
+    writer_config = next(
+        item for item in envelope["evidence"] if item["kind"] == 
"flink_writer_config"
+    )
+    writer_config["facts"].update(
+        {
+            "write_concurrency_mode": "OPTIMISTIC_CONCURRENCY_CONTROL",
+            "lock_provider_class": (
+                "org.apache.hudi.client.transaction.lock."
+                "FileSystemBasedLockProvider"
+            ),
+            "lock_configuration_consistency": "CONSISTENT",
+        }
+    )
+    conflicting_config = copy.deepcopy(writer_config)
+    conflicting_config["evidence_id"] = "writer-config-2"
+    conflicting_config["sha256"] = "7" * 64
+    conflicting_config["facts"]["lock_provider_class"] = (
+        "org.apache.hudi.client.transaction.lock.ZookeeperBasedLockProvider"
+    )
+    envelope["evidence"].append(conflicting_config)
+
+    result = _run_flink_existing_table_validator(_write_design(tmp_path, 
envelope))
+
+    assert result.returncode == 1
+    assessment = json.loads(result.stdout)
+    assert assessment["status"] == "REVIEW_REQUIRED"
+    assert assessment["reconciliation_result"] == "UNVERIFIED"
+    assert assessment["change_disposition"] == "UNVERIFIED"
+    assert assessment["writer_contract_result"] == "CONFLICT"
+    assert "FLINK_EXISTING_EVIDENCE_CONFLICT" in assessment["finding_codes"]
+    assert "FLINK_WRITER_CONTRACT_CONFLICT" in assessment["finding_codes"]
+    assert assessment["executable_eligible"] is False
+
+
 @pytest.mark.parametrize(
     ("field", "value"),
     [
@@ -3130,7 +3907,7 @@ def test_safety_gate_order_is_stable() -> None:
         "## F9 — Streaming checkpoint contract",
         "## F10 — Mutable table type, query freshness, and MOR ownership",
         "## F11 — Existing-table evidence and writer transition",
-        "## PR2/PR3/PR4/PR5/PR6 validation and completion",
+        "## PR2/PR3/PR4/PR5/PR6/PR7 validation and completion",
     ]
     offsets = [flow.index(heading) for heading in headings]
     assert offsets == sorted(offsets)
diff --git a/hudi-ai-operator/skills/hudi-architect/README.md 
b/hudi-ai-operator/skills/hudi-architect/README.md
index fbd437b75272..50edb8333403 100644
--- a/hudi-ai-operator/skills/hudi-architect/README.md
+++ b/hudi-ai-operator/skills/hudi-architect/README.md
@@ -44,7 +44,9 @@ Target Hudi version: **1.2.0**.
   an existing table backed by structured evidence. For new tables, those same 
design schemas can
   compose direct Hive sync, Hudi DFS Catalog, or external Hudi HMS Catalog 
through five pinned
   profiles. Glue, managed HMS, existing-table catalog mutation, and live 
connectivity claims
-  remain fail-closed.
+  remain fail-closed. A versioned PR7 Writer Contract can record independent 
writers and ownership,
+  but remains assessment-only: OCC is unverified, current schemas are 
incompatible with NBCC, and
+  no multi-writer SQL is emitted.
 
 ## How to invoke
 
@@ -93,6 +95,8 @@ Even without invoking as a Skill, the files in `references/` 
are readable design
   rules for the assessment-only existing-table path.
 - `flink-catalog-composition.md` — the five PR6 profiles, lifecycle/path 
rules, Hive sync ownership,
   MOR naming, freshness boundary, and deployment checks.
+- `flink-writer-contract.md` — the PR7 writer inventory, ownership, 
observed-concurrency, evidence,
+  and non-executable capability rules.
 
 The Skill itself is defined in `SKILL.md`.
 
@@ -136,6 +140,8 @@ The mutable MOR schema-3 fixture is
 `hudi-agent-gateway/tests/fixtures/hudi_architect/flink_pr4/mutable_mor.json`.
 PR6 profile inputs are recorded in
 
`hudi-agent-gateway/tests/fixtures/hudi_architect/flink_pr6/catalog_profiles.toml`.
+PR7 source-backed concurrency fixtures are implemented in
+`hudi-agent-gateway/fixtures/hudi-architect-flink-1.20/src/test/java/org/apache/hudi/agent/architect/TestFlinkArchitectWriterContractFixtures.java`.
 
 Validate a structured existing-table reconciliation contract without rendering 
SQL:
 
@@ -160,18 +166,20 @@ This is **Milestone 1 of a longer arc** — meant to be 
shareable and playable,
 4. **Read the ADR output.** Are the revisit conditions actually measurable? Do 
the durability tables cover the one-way decisions relevant to your workload?
 5. **Declare a second writer.** On the Spark route, check the selected lock 
provider, emitted block,
    and pre-launch checklist. On the Flink route, check that the result is 
`REVIEW_REQUIRED` and
-   that no concurrency-sensitive configuration is emitted.
+   that the Writer Contract is normalized without emitting 
concurrency-sensitive configuration.
 
 ## What's out of scope in Milestone 1
 
 - Flink append-only MOR, standalone compaction, detailed compaction tuning, 
clustering,
   existing-table SQL generation or catalog takeover, AWS Glue, 
managed/embedded HMS, live catalog
-  probing, multiple writers, alternative indexes, custom
+  probing, executable multi-writer operation, alternative indexes, custom
   mergers, retract normalization, partition movement, key-only deletes, full 
CDC history,
   DataStream API, `HoodieFlinkStreamer`, source connector generation, and 
submit/deploy commands.
   Those paths do not reuse the bounded SQL output as if it were generally 
applicable.
 
-- Multi-writer contention tuning — the mode, lock provider, and config bundle 
are derived, but retry/timeout tuning and early conflict detection stay at 
defaults (they depend on observed behavior, not design-time facts).
+- Multi-writer contention tuning — the Spark route may derive its established 
bundle; the Flink
+  Writer Contract records only observed mode/provider facts. Retry/timeout 
tuning and early
+  conflict detection depend on observed behavior, not design-time facts.
 - Benchmarking / scale-characterization — different flow shape, future 
revision.
 - CONSISTENT_HASHING bucket recommendations at design time.
 - Partial-update MERGE nudging (Spark SQL only).
diff --git a/hudi-ai-operator/skills/hudi-architect/RUNBOOK.md 
b/hudi-ai-operator/skills/hudi-architect/RUNBOOK.md
index 9647fc762956..93a589c6f11d 100644
--- a/hudi-ai-operator/skills/hudi-architect/RUNBOOK.md
+++ b/hudi-ai-operator/skills/hudi-architect/RUNBOOK.md
@@ -29,7 +29,9 @@ bounded append-only COW, mutable COW, and mutable MOR 
new-table, single-writer,
 streaming paths. New-table designs can optionally compose direct Hive sync, 
Hudi DFS Catalog, or
 external Hudi HMS Catalog through five pinned profiles. For an existing table, 
it can instead
 produce a read-only exact-match reconciliation report from provenance-bearing 
table, schema,
-writer, and timeline evidence; that report never contains SQL or mutation 
commands.
+writer, and timeline evidence; that report never contains SQL or mutation 
commands. For a
+confirmed independent writer, PR7 records a non-executable Writer Contract and 
deployment checks
+without selecting a concurrency mode, lock provider, or index.
 
 ---
 
@@ -155,6 +157,8 @@ explicit-column `INSERT INTO`. The assessment retains 
effective catalog configur
 deployment-check codes separately; generated SQL is never executed and 
deployment remains manual.
 An existing-table session emits comparisons, evidence provenance, findings, 
and deployment rechecks
 with executable eligibility `false`, including when the static result is 
`COMPATIBLE`.
+A multi-writer session additionally reports Writer Contract completeness and 
an `UNVERIFIED` or
+`UNSUPPORTED` capability. It never reports multi-writer support or emits SQL.
 
 Then: land a first commit in a staging path, run your real read patterns 
against it, and check the ADR's operational playbook section for what to 
monitor from day one (commit duration, pending compactions, active timeline 
size, small-file ratio).
 
@@ -166,12 +170,14 @@ Be aware of what the agent will *decline* to decide — it 
defers honestly rathe
   new-table, single-writer streaming SQL sinks are implemented, with five 
direct SQL / DFS Catalog
   / HMS Catalog compositions. Append-only MOR, standalone compaction, detailed 
compaction tuning,
   clustering, existing-table SQL or takeover, existing-table catalog mutation, 
AWS Glue, managed
-  or embedded HMS, live catalog probing, multiple writers, alternative 
indexes, custom mergers,
+  or embedded HMS, live catalog probing, executable multi-writer operation, 
alternative indexes,
+  custom mergers,
   retract normalization, partition movement, key-only deletes, full CDC 
history, DataStream API,
   `HoodieFlinkStreamer`, source connector generation, and submit/deploy 
commands remain out of
-  scope.
+  scope. PR7 can inventory multiple writers, but OCC remains unverified and 
NBCC is incompatible
+  with the current COW and FLINK_STATE-indexed MOR contracts.
 
-- **Multi-writer contention tuning** — on the Spark route, the agent asks 
whether anything else writes the table (every tier but the lightest), derives 
the concurrency mode, picks a lock provider, and emits a complete, runnable 
block. What it does *not* do is tune for observed contention: lock 
retry/timeout values, conflict-retry counts, and early conflict detection stay 
at their defaults, because the right values depend on measured behavior rather 
than design-time facts. That is Operati [...]
+- **Multi-writer contention tuning** — on the Spark route, the agent asks 
whether anything else writes the table (every tier but the lightest), derives 
the concurrency mode, picks a lock provider, and emits a complete, runnable 
block. What it does *not* do is tune for observed contention: lock 
retry/timeout values, conflict-retry counts, and early conflict detection stay 
at their defaults, because the right values depend on measured behavior rather 
than design-time facts. That is Operati [...]
 - **Catalog / metastore sync beyond the common paths** — on the Spark route, 
the agent asks which engines query the table and derives sync config for Hive 
Metastore, AWS Glue, BigQuery, and DataHub, including the partition-extractor 
and MOR `_ro`/`_rt` consequences. The Flink route emits only the five pinned 
direct SQL / DFS Catalog / external HMS Catalog profiles and treats catalog 
visibility as post-commit eventual. Not covered: Glue execution, live HMS 
checks, existing-table registrat [...]
 - **Non-Kafka source configs** — Kafka sources get full source-class + 
schema-provider derivation; for DFS/JDBC/Pulsar/Kinesis sources you'll fill in 
the `--source-class` and source properties from Hudi docs.
 - **Cross-format interoperability (Apache XTable)** — explained and linked, 
not configured. XTable translates Hudi metadata so the same files can be read 
as Iceberg or Delta, but it is a separate incubating project: its config keys 
live in its codebase rather than Hudi's, so this skill cannot machine-verify 
them the way it verifies every `hoodie.*` key it emits. The agent names the one 
durable coupling — **XTable's FAQ lists MOR as unsupported, so cross-format 
interop argues for CoW**, w [...]
diff --git a/hudi-ai-operator/skills/hudi-architect/SKILL.md 
b/hudi-ai-operator/skills/hudi-architect/SKILL.md
index 8d369d389a6d..bfcb78873dac 100644
--- a/hudi-ai-operator/skills/hudi-architect/SKILL.md
+++ b/hudi-ai-operator/skills/hudi-architect/SKILL.md
@@ -69,6 +69,9 @@ Within the Flink route, load the remaining references only 
when needed:
   table; use it to collect provenance-bearing evidence and produce the 
assessment-only PR5 report.
 - Load `references/flink-catalog-composition.md` only when the user needs 
named catalog resolution
   or external discovery; use its PR6 contract without changing design schema 
1, 2, or 3.
+- Load `references/flink-writer-contract.md` only after F2 confirms an 
independent writer or
+  standalone service; collect its PR7 facts without selecting concurrency, 
locking, or index
+  settings.
 
 The verified Flink baseline is Apache Hudi 1.2.0 at source revision
 `f05c83f2b97732de7a558ff9b26959e1139c05f5` with Apache Flink 1.20; build and 
test fixtures use
@@ -80,7 +83,8 @@ another Hudi or Flink version, do not silently reuse the 
baseline: add a `REVIEW
 unless a separate verified capability reference exists.
 
 The Flink implementation includes the PR1 safety foundation, three bounded 
executable paths, one
-existing-table assessment path, and PR6 catalog/metastore composition. PR2 
append-only COW, PR3
+existing-table assessment path, PR6 catalog/metastore composition, and the PR7 
assessment-only
+multi-writer Writer Contract. PR2 append-only COW, PR3
 mutable COW, and PR4 mutable MOR all require a new table, confirmed single 
writer, streaming
 checkpointing, and `CONFIG_VALIDATED` from `validate_flink_design.py`. They 
retain direct SQL by
 default; PR6 may compose one of five pinned direct/DFS/HMS profiles without 
changing the table
@@ -93,8 +97,10 @@ or 3 desired contract with structured evidence from an 
existing table, but it ne
 authorizes mutation. PR6 supports new-table direct Hive sync, Hudi DFS 
Catalog, and external Hudi
 HMS Catalog composition; AWS Glue, managed HMS, existing-table catalog 
mutation, and live service
 claims fail closed. Append-only MOR, standalone table services, detailed 
compaction tuning,
-separate-writer or cross-engine takeover, multi-writer, alternative indexes, 
custom mergers,
-retract normalization, and full CDC history still fail closed.
+separate-writer or cross-engine takeover, multi-writer executable output, 
alternative indexes,
+custom mergers, retract normalization, and full CDC history still fail closed. 
A confirmed
+multi-writer topology can be recorded by PR7, but OCC remains unverified and 
the existing
+FLINK_STATE paths are incompatible with NBCC; the validator never chooses a 
replacement.
 
 ## Flow structure
 
@@ -104,7 +110,8 @@ The conversation has three parts:
 2. **Rounds 1-3** — workload questions, gated conditionally by tier.
 3. **Output** — engine-specific output: the established Spark artifacts, a 
non-executable Flink
   assessment, a PR5 existing-table reconciliation report, or bounded 
validator-rendered
-  PR2/PR3/PR4 SQL artifacts, optionally composed with validated PR6 catalog 
setup SQL.
+  PR2/PR3/PR4 SQL artifacts, optionally composed with validated PR6 catalog 
setup SQL. PR7 adds
+  only a non-executable Writer Contract assessment.
 
 Load `references/question-flow.md` for the full round-by-round question list 
with conditional gating.
 
@@ -224,6 +231,10 @@ When a catalog block is present, retain the validator's 
`catalog_setup_sql`, eff
 configuration, and deployment checks as separate machine fields; `--emit-sql` 
is only their
 canonical ordered composition. Catalog deployment checks never imply that a 
live service was
 probed, and the generated SQL is never executed by this skill.
+When F2 confirms another writer, add the version-1 `writer_contract` described 
by
+`flink-writer-contract.md`. Retain its normalized contract, capability notes, 
and deployment checks
+as assessment fields. Never turn `COMPLETE` into a support claim: multi-writer 
capability remains
+`UNVERIFIED` or `UNSUPPORTED`, and executable eligibility remains `false`.
 For an existing table, create the same explicit desired contract, collect and 
confirm evidence per
 `flink-existing-table-reconciliation.md`, and run 
`validate_flink_existing_table.py` instead. Emit
 its report and deployment checks exactly as assessment data; never reinterpret 
`COMPATIBLE` as
@@ -284,6 +295,8 @@ fact `INCOMPLETE`; do not ask the user to reveal the secret.
 - Hand-write, repair, or extend SQL after `validate_flink_design.py` renders 
it.
 - Claim catalog/commit atomicity, execute generated catalog SQL, probe live 
HMS/Glue, or emit
   existing-table catalog mutations.
+- Recommend or emit an OCC/NBCC mode, lock provider, alternative index, or 
multi-writer SQL from a
+  Writer Contract assessment.
 - Treat a successful `HoodieTableFactory` construction as proof that the 
source changelog is
   append-only.
 
diff --git a/hudi-ai-operator/skills/hudi-architect/flink_writer_contract.py 
b/hudi-ai-operator/skills/hudi-architect/flink_writer_contract.py
new file mode 100644
index 000000000000..cf8a4628fefb
--- /dev/null
+++ b/hudi-ai-operator/skills/hudi-architect/flink_writer_contract.py
@@ -0,0 +1,767 @@
+#!/usr/bin/env python3
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Assess the bounded Flink multi-writer Writer Contract.
+
+This module validates explicit writer facts.  It does not select a concurrency
+mode, lock provider, index, failed-writes policy, or recovery procedure, and it
+never makes a multi-writer design executable.
+"""
+
+from __future__ import annotations
+
+from typing import Any, TypeGuard
+
+WRITER_CONTRACT_SCHEMA_VERSION = 1
+
+WRITER_ROLES = frozenset(
+    {
+        "INGESTION",
+        "BACKFILL",
+        "CLEANUP",
+        "STANDALONE_COMPACTION",
+        "STANDALONE_CLUSTERING",
+        "OTHER",
+    }
+)
+WRITER_RELATIONSHIPS = frozenset({"INDEPENDENT_CONCURRENT", "MAY_OVERLAP"})
+WRITER_ENGINES = frozenset({"FLINK", "SPARK", "OTHER", "UNKNOWN"})
+WRITE_OPERATIONS = frozenset(
+    {
+        "INSERT",
+        "INSERT_PREPPED",
+        "UPSERT",
+        "UPSERT_PREPPED",
+        "BULK_INSERT",
+        "BULK_INSERT_PREPPED",
+        "DELETE",
+        "DELETE_PREPPED",
+        "BOOTSTRAP",
+        "INSERT_OVERWRITE",
+        "BUCKET_RESCALE",
+        "CLUSTER",
+        "DELETE_PARTITION",
+        "INSERT_OVERWRITE_TABLE",
+        "COMPACT",
+        "INDEX",
+        "ALTER_SCHEMA",
+        "LOG_COMPACT",
+        "UNKNOWN",
+    }
+)
+CONCURRENCY_MODES = frozenset(
+    {
+        "SINGLE_WRITER",
+        "OPTIMISTIC_CONCURRENCY_CONTROL",
+        "NON_BLOCKING_CONCURRENCY_CONTROL",
+        "UNKNOWN",
+    }
+)
+LOCK_CONFIGURATION_CONSISTENCY = frozenset({"CONSISTENT", "CONFLICTING", 
"UNKNOWN"})
+RESERVED_OWNER_IDS = frozenset({"EXTERNAL_CONTROL_PLANE", "UNKNOWN"})
+
+_CONTRACT_KEYS = {
+    "contract_schema",
+    "primary_writer_id",
+    "additional_writers",
+    "ownership",
+    "observed_concurrency",
+}
+_WRITER_KEYS = {
+    "writer_id",
+    "role",
+    "relationship_to_primary",
+    "engine",
+    "engine_version",
+    "hudi_version",
+    "operation",
+}
+_OWNERSHIP_KEYS = {
+    "schema_evolution_owner_id",
+    "table_configuration_owner_id",
+}
+_CONCURRENCY_KEYS = {
+    "mode",
+    "lock_provider_class",
+    "lock_configuration_consistency",
+}
+
+
+def _non_empty_string(value: Any) -> TypeGuard[str]:
+    return isinstance(value, str) and bool(value.strip())
+
+
+def _finding(code: str, message: str) -> dict[str, str]:
+    return {"code": code, "message": message}
+
+
+def _add_finding(findings: list[dict[str, str]], code: str, message: str) -> 
None:
+    existing = next((item for item in findings if item["code"] == code), None)
+    if existing is None:
+        findings.append(_finding(code, message))
+    elif message not in existing["message"]:
+        existing["message"] += f"; {message}"
+
+
+def _invalid(findings: list[dict[str, str]], message: str) -> None:
+    _add_finding(findings, "FLINK_WRITER_CONTRACT_INVALID", message)
+
+
+def _check_keys(
+    value: dict[str, Any], allowed: set[str], field: str, findings: 
list[dict[str, str]]
+) -> None:
+    unknown = sorted(set(value) - allowed)
+    if unknown:
+        _invalid(findings, f"{field} contains unsupported fields: {unknown}")
+
+
+def _deployment_checks() -> list[dict[str, Any]]:
+    return [
+        {"check": "verify_concurrent_writer_inventory", "required": True},
+        {"check": "verify_uniform_concurrency_mode", "required": True},
+        {"check": "verify_lock_provider_configuration", "required": True},
+        {"check": "verify_lazy_failed_writes_cleaner_policy", "required": 
True},
+        {"check": "verify_writer_recovery_procedure", "required": True},
+        {"check": "verify_schema_evolution_ownership", "required": True},
+    ]
+
+
+def _not_applicable(
+    findings: list[dict[str, str]],
+    *,
+    capability: str = "NOT_APPLICABLE",
+    notes: list[dict[str, str]] | None = None,
+) -> dict[str, Any]:
+    return {
+        "writer_contract_result": "NOT_APPLICABLE",
+        "multi_writer_capability": capability,
+        "normalized_writer_contract": {},
+        "writer_capability_notes": notes or [],
+        "writer_deployment_checks": [],
+        "findings": findings,
+    }
+
+
+def _normalize_writer(
+    raw_writer: Any,
+    index: int,
+    findings: list[dict[str, str]],
+) -> dict[str, str] | None:
+    field = f"writer_contract.additional_writers[{index}]"
+    if not isinstance(raw_writer, dict):
+        _invalid(findings, f"{field} must be an object")
+        return None
+    _check_keys(raw_writer, _WRITER_KEYS, field, findings)
+
+    normalized: dict[str, str] = {}
+    for name in sorted(_WRITER_KEYS):
+        value = raw_writer.get(name)
+        if not _non_empty_string(value):
+            _invalid(findings, f"{field}.{name} must be a non-empty string")
+            continue
+        normalized[name] = value.strip()
+
+    enum_fields = {
+        "role": WRITER_ROLES,
+        "relationship_to_primary": WRITER_RELATIONSHIPS,
+        "engine": WRITER_ENGINES,
+        "operation": WRITE_OPERATIONS,
+    }
+    for name, allowed in enum_fields.items():
+        value = normalized.get(name)
+        if value is not None and value not in allowed:
+            _invalid(
+                findings,
+                f"{field}.{name} must be one of {sorted(allowed)}",
+            )
+    return normalized if set(normalized) == _WRITER_KEYS else None
+
+
+def _normalize_ownership(
+    raw_ownership: Any,
+    writer_ids: set[str],
+    findings: list[dict[str, str]],
+) -> dict[str, str] | None:
+    field = "writer_contract.ownership"
+    if not isinstance(raw_ownership, dict):
+        _invalid(findings, f"{field} must be an object")
+        return None
+    _check_keys(raw_ownership, _OWNERSHIP_KEYS, field, findings)
+
+    normalized: dict[str, str] = {}
+    schema_owner = raw_ownership.get("schema_evolution_owner_id")
+    if not _non_empty_string(schema_owner):
+        _invalid(findings, f"{field}.schema_evolution_owner_id is required")
+    else:
+        normalized["schema_evolution_owner_id"] = schema_owner.strip()
+
+    table_owner = raw_ownership.get("table_configuration_owner_id")
+    if table_owner is not None:
+        if not _non_empty_string(table_owner):
+            _invalid(
+                findings,
+                f"{field}.table_configuration_owner_id must be a non-empty 
string",
+            )
+        else:
+            normalized["table_configuration_owner_id"] = table_owner.strip()
+
+    for name, owner_id in normalized.items():
+        if owner_id not in writer_ids | RESERVED_OWNER_IDS:
+            _invalid(
+                findings,
+                f"{field}.{name} must reference a declared writer or a 
reserved owner",
+            )
+        elif owner_id == "UNKNOWN":
+            _add_finding(
+                findings,
+                "FLINK_WRITER_OWNERSHIP_UNRESOLVED",
+                f"{field}.{name} is unresolved",
+            )
+    return normalized if "schema_evolution_owner_id" in normalized else None
+
+
+def _normalize_concurrency(
+    raw_concurrency: Any,
+    findings: list[dict[str, str]],
+) -> dict[str, str] | None:
+    if raw_concurrency is None:
+        return None
+    field = "writer_contract.observed_concurrency"
+    if not isinstance(raw_concurrency, dict):
+        _invalid(findings, f"{field} must be an object")
+        return None
+    _check_keys(raw_concurrency, _CONCURRENCY_KEYS, field, findings)
+    normalized: dict[str, str] = {}
+    for name in sorted(_CONCURRENCY_KEYS):
+        value = raw_concurrency.get(name)
+        if not _non_empty_string(value):
+            _invalid(findings, f"{field}.{name} must be a non-empty string")
+            continue
+        normalized[name] = value.strip()
+    mode = normalized.get("mode")
+    if mode is not None and mode not in CONCURRENCY_MODES:
+        _invalid(findings, f"{field}.mode must be one of 
{sorted(CONCURRENCY_MODES)}")
+    consistency = normalized.get("lock_configuration_consistency")
+    if consistency is not None and consistency not in 
LOCK_CONFIGURATION_CONSISTENCY:
+        _invalid(
+            findings,
+            f"{field}.lock_configuration_consistency must be one of "
+            f"{sorted(LOCK_CONFIGURATION_CONSISTENCY)}",
+        )
+    return normalized if set(normalized) == _CONCURRENCY_KEYS else None
+
+
+def _add_note(notes: list[dict[str, str]], code: str, message: str) -> None:
+    if not any(note["code"] == code for note in notes):
+        notes.append({"code": code, "message": message})
+
+
+def _assess_contract_concurrency(
+    concurrency: dict[str, Any],
+    findings: list[dict[str, str]],
+    notes: list[dict[str, str]],
+) -> tuple[bool, bool]:
+    """Assess explicit multi-writer contract concurrency facts."""
+
+    conflict = False
+    unsupported = False
+    mode = concurrency.get("mode")
+    consistency = concurrency.get("lock_configuration_consistency")
+    if mode == "SINGLE_WRITER":
+        _add_finding(
+            findings,
+            "FLINK_WRITER_CONTRACT_CONFLICT",
+            "A multi-writer contract cannot observe SINGLE_WRITER concurrency 
mode",
+        )
+        conflict = True
+    elif mode == "OPTIMISTIC_CONCURRENCY_CONTROL":
+        _add_note(
+            notes,
+            "FLINK_OCC_DEPLOYMENT_UNVERIFIED",
+            "OCC is recognized by the pinned source, but concurrent, lock, and 
"
+            "recovery behavior is not exercised by this assessment.",
+        )
+    elif mode == "NON_BLOCKING_CONCURRENCY_CONTROL":
+        unsupported = True
+        _add_finding(
+            findings,
+            "FLINK_WRITER_COMBINATION_UNSUPPORTED",
+            "The existing COW or FLINK_STATE-indexed MOR paths do not satisfy 
the "
+            "pinned NBCC requirement for MOR with simple bucket index",
+        )
+    if consistency == "CONFLICTING":
+        _add_finding(
+            findings,
+            "FLINK_WRITER_CONTRACT_CONFLICT",
+            "Observed lock configuration is conflicting across writers",
+        )
+        conflict = True
+    return conflict, unsupported
+
+
+def _evidence_concurrency(
+    evidence: dict[str, Any],
+) -> dict[str, Any]:
+    return {
+        name: evidence[field]
+        for name, field in {
+            "mode": "flink_writer_config.write_concurrency_mode",
+            "lock_provider_class": "flink_writer_config.lock_provider_class",
+            "lock_configuration_consistency": (
+                "flink_writer_config.lock_configuration_consistency"
+            ),
+        }.items()
+        if field in evidence
+    }
+
+
+def _assess_evidence_concurrency(
+    concurrency: dict[str, Any],
+    writer_model: Any,
+    findings: list[dict[str, str]],
+    notes: list[dict[str, str]],
+) -> tuple[bool, bool, bool, bool]:
+    """Assess observed concurrency independently of Writer Contract 
validity."""
+
+    if not concurrency:
+        return False, False, False, False
+
+    conflict = False
+    unsupported = False
+    relevant = False
+    conflicting_lock_evidence = False
+    mode = concurrency.get("mode")
+    consistency = concurrency.get("lock_configuration_consistency")
+    if mode == "SINGLE_WRITER":
+        relevant = writer_model == "multi_writer"
+        if writer_model == "multi_writer":
+            _add_finding(
+                findings,
+                "FLINK_WRITER_CONTRACT_CONFLICT",
+                "A multi-writer design cannot observe SINGLE_WRITER 
concurrency mode",
+            )
+            conflict = True
+    elif mode == "OPTIMISTIC_CONCURRENCY_CONTROL":
+        relevant = True
+        _add_note(
+            notes,
+            "FLINK_OCC_DEPLOYMENT_UNVERIFIED",
+            "OCC is recognized by the pinned source, but concurrent, lock, and 
"
+            "recovery behavior is not exercised by this assessment.",
+        )
+        if writer_model != "multi_writer":
+            _add_finding(
+                findings,
+                "FLINK_MULTI_WRITER_REVIEW",
+                "Observed concurrency configuration requires multi-writer 
review",
+            )
+    elif mode == "NON_BLOCKING_CONCURRENCY_CONTROL":
+        relevant = True
+        unsupported = True
+        _add_finding(
+            findings,
+            "FLINK_WRITER_COMBINATION_UNSUPPORTED",
+            "The existing COW or FLINK_STATE-indexed MOR paths do not satisfy 
the "
+            "pinned NBCC requirement for MOR with simple bucket index",
+        )
+        if writer_model != "multi_writer":
+            _add_finding(
+                findings,
+                "FLINK_MULTI_WRITER_REVIEW",
+                "Observed concurrency configuration requires multi-writer 
review",
+            )
+
+    if consistency == "CONFLICTING":
+        relevant = True
+        conflicting_lock_evidence = True
+        _add_finding(
+            findings,
+            "FLINK_EXISTING_EVIDENCE_CONFLICT",
+            "Observed lock configuration is conflicting across writers",
+        )
+        if writer_model != "multi_writer":
+            _add_finding(
+                findings,
+                "FLINK_MULTI_WRITER_REVIEW",
+                "Observed concurrency configuration requires multi-writer 
review",
+            )
+    return conflict, unsupported, relevant, conflicting_lock_evidence
+
+
+def _writer_topology_evidence_findings(
+    writer_model: Any,
+    evidence: dict[str, Any],
+    conflicting_evidence_fields: set[str],
+    evidence_source_ids: dict[str, set[str]],
+    findings: list[dict[str, str]],
+) -> None:
+    inventory_fields = {
+        "writer_inventory.active_writers",
+        "writer_inventory.concurrent_writers",
+    }
+    conflicting = sorted(inventory_fields & conflicting_evidence_fields)
+    if conflicting:
+        _add_finding(
+            findings,
+            "FLINK_EXISTING_EVIDENCE_CONFLICT",
+            f"Existing-table evidence disagrees about writer inventory: 
{conflicting}",
+        )
+
+    active_writers = evidence.get("writer_inventory.active_writers")
+    concurrent_writers = evidence.get("writer_inventory.concurrent_writers")
+    multiple_active = isinstance(active_writers, list) and len(active_writers) 
> 1
+    if writer_model != "multi_writer" and (
+        multiple_active or concurrent_writers is True
+    ):
+        _add_finding(
+            findings,
+            "FLINK_MULTI_WRITER_REVIEW",
+            "Existing-table writer inventory requires multi-writer review",
+        )
+    active_sources = 
evidence_source_ids.get("writer_inventory.active_writers", set())
+    concurrent_sources = evidence_source_ids.get(
+        "writer_inventory.concurrent_writers", set()
+    )
+    if (
+        multiple_active
+        and concurrent_writers is False
+        and bool(active_sources & concurrent_sources)
+    ):
+        _add_finding(
+            findings,
+            "FLINK_EXISTING_EVIDENCE_CONFLICT",
+            "Writer inventory reports multiple active writers while 
concurrent_writers is false",
+        )
+
+
+def _evidence_conflicts(
+    normalized: dict[str, Any],
+    evidence: dict[str, Any],
+    conflicting_evidence_fields: set[str],
+    findings: list[dict[str, str]],
+) -> bool:
+    contract_fact_fields = {
+        "writer_inventory.active_writers",
+        "writer_inventory.schema_evolution_owner_id",
+        "flink_writer_config.write_concurrency_mode",
+        "flink_writer_config.lock_provider_class",
+        "flink_writer_config.lock_configuration_consistency",
+    }
+    conflicting = sorted(contract_fact_fields & conflicting_evidence_fields)
+    conflict = False
+    if conflicting:
+        _add_finding(
+            findings,
+            "FLINK_WRITER_CONTRACT_CONFLICT",
+            f"Existing-table evidence disagrees about Writer Contract facts: 
{conflicting}",
+        )
+        conflict = True
+
+    declared_writer_ids = {
+        normalized["primary_writer_id"],
+        *(writer["writer_id"] for writer in normalized["additional_writers"]),
+    }
+    expected: dict[str, Any] = {
+        "writer_inventory.schema_evolution_owner_id": normalized["ownership"][
+            "schema_evolution_owner_id"
+        ],
+    }
+    concurrency = normalized.get("observed_concurrency")
+    if isinstance(concurrency, dict):
+        expected.update(
+            {
+                "flink_writer_config.write_concurrency_mode": 
concurrency["mode"],
+                "flink_writer_config.lock_provider_class": concurrency[
+                    "lock_provider_class"
+                ],
+                "flink_writer_config.lock_configuration_consistency": 
concurrency[
+                    "lock_configuration_consistency"
+                ],
+            }
+        )
+
+    mismatches = [
+        field
+        for field, observed in evidence.items()
+        if field in expected and observed != expected[field]
+    ]
+    active_writers = evidence.get("writer_inventory.active_writers")
+    if isinstance(active_writers, list) and not set(active_writers) <= 
declared_writer_ids:
+        mismatches.append("writer_inventory.active_writers")
+    mismatches = sorted(mismatches)
+    if mismatches:
+        _add_finding(
+            findings,
+            "FLINK_WRITER_CONTRACT_CONFLICT",
+            f"Existing-table evidence conflicts with Writer Contract facts: 
{mismatches}",
+        )
+        conflict = True
+    return conflict
+
+
+def assess_writer_contract(
+    design: dict[str, Any],
+    *,
+    evidence: dict[str, Any] | None = None,
+    conflicting_evidence_fields: set[str] | None = None,
+    evidence_source_ids: dict[str, set[str]] | None = None,
+) -> dict[str, Any]:
+    """Return a deterministic, assessment-only Writer Contract result."""
+
+    findings: list[dict[str, str]] = []
+    raw_safety = design.get("safety")
+    safety = raw_safety if isinstance(raw_safety, dict) else {}
+    writer_model = safety.get("writer_model")
+    raw_contract = design.get("writer_contract")
+    supplied_evidence = evidence or {}
+    supplied_conflicts = conflicting_evidence_fields or set()
+    supplied_sources = evidence_source_ids or {}
+    notes: list[dict[str, str]] = []
+
+    _writer_topology_evidence_findings(
+        writer_model,
+        supplied_evidence,
+        supplied_conflicts,
+        supplied_sources,
+        findings,
+    )
+    (
+        evidence_conflict,
+        evidence_unsupported,
+        evidence_relevant,
+        conflicting_lock_evidence,
+    ) = _assess_evidence_concurrency(
+        _evidence_concurrency(supplied_evidence),
+        writer_model,
+        findings,
+        notes,
+    )
+    capability = (
+        "UNSUPPORTED"
+        if evidence_unsupported
+        else "UNVERIFIED"
+        if writer_model == "multi_writer" or evidence_relevant
+        else "NOT_APPLICABLE"
+    )
+
+    if writer_model == "single_writer":
+        if raw_contract is not None:
+            _add_finding(
+                findings,
+                "FLINK_WRITER_CONTRACT_CONFLICT",
+                "writer_contract must be absent for a confirmed single-writer 
design",
+            )
+            result = _not_applicable(findings, capability=capability, 
notes=notes)
+            result["writer_contract_result"] = "CONFLICT"
+            return result
+        return _not_applicable(findings, capability=capability, notes=notes)
+
+    if writer_model != "multi_writer":
+        if raw_contract is not None:
+            _add_finding(
+                findings,
+                "FLINK_WRITER_CONTRACT_CONFLICT",
+                "Writer topology must be resolved before a Writer Contract is 
assessed",
+            )
+        return _not_applicable(findings, capability=capability, notes=notes)
+
+    _add_finding(
+        findings,
+        "FLINK_MULTI_WRITER_REVIEW",
+        "Multi-writer execution remains assessment-only and requires 
deployment review",
+    )
+    if raw_contract is None:
+        _add_finding(
+            findings,
+            "FLINK_WRITER_CONTRACT_REQUIRED",
+            "A Writer Contract is required for a confirmed multi-writer 
design",
+        )
+        return {
+            "writer_contract_result": "INCOMPLETE",
+            "multi_writer_capability": capability,
+            "normalized_writer_contract": {},
+            "writer_capability_notes": notes,
+            "writer_deployment_checks": _deployment_checks(),
+            "findings": findings,
+        }
+    if not isinstance(raw_contract, dict):
+        _invalid(findings, "writer_contract must be an object")
+        return {
+            "writer_contract_result": "INCOMPLETE",
+            "multi_writer_capability": capability,
+            "normalized_writer_contract": {},
+            "writer_capability_notes": notes,
+            "writer_deployment_checks": _deployment_checks(),
+            "findings": findings,
+        }
+
+    _check_keys(raw_contract, _CONTRACT_KEYS, "writer_contract", findings)
+    contract_schema = raw_contract.get("contract_schema")
+    if (
+        type(contract_schema) is not int
+        or contract_schema != WRITER_CONTRACT_SCHEMA_VERSION
+    ):
+        _invalid(
+            findings,
+            f"writer_contract.contract_schema must be 
{WRITER_CONTRACT_SCHEMA_VERSION}",
+        )
+
+    primary_writer_id = raw_contract.get("primary_writer_id")
+    if not _non_empty_string(primary_writer_id):
+        _invalid(findings, "writer_contract.primary_writer_id is required")
+        normalized_primary_id = ""
+    else:
+        normalized_primary_id = primary_writer_id.strip()
+
+    raw_writers = raw_contract.get("additional_writers")
+    normalized_writers: list[dict[str, str]] = []
+    if not isinstance(raw_writers, list) or not raw_writers:
+        _invalid(
+            findings,
+            "writer_contract.additional_writers must be a non-empty array",
+        )
+    else:
+        for index, raw_writer in enumerate(raw_writers):
+            normalized_writer = _normalize_writer(raw_writer, index, findings)
+            if normalized_writer is not None:
+                normalized_writers.append(normalized_writer)
+
+    writer_ids = [writer["writer_id"] for writer in normalized_writers]
+    if len(writer_ids) != len(set(writer_ids)):
+        _invalid(
+            findings,
+            "writer_contract.additional_writers has duplicate writer_id 
values",
+        )
+    if normalized_primary_id and normalized_primary_id in writer_ids:
+        _invalid(
+            findings,
+            "writer_contract.primary_writer_id must differ from every 
additional writer_id",
+        )
+
+    declared_writer_ids = set(writer_ids)
+    if normalized_primary_id:
+        declared_writer_ids.add(normalized_primary_id)
+    ownership = _normalize_ownership(
+        raw_contract.get("ownership"), declared_writer_ids, findings
+    )
+    concurrency = _normalize_concurrency(
+        raw_contract.get("observed_concurrency"), findings
+    )
+
+    invalid = any(
+        finding["code"] == "FLINK_WRITER_CONTRACT_INVALID" for finding in 
findings
+    )
+    if invalid:
+        return {
+            "writer_contract_result": "INCOMPLETE",
+            "multi_writer_capability": capability,
+            "normalized_writer_contract": {},
+            "writer_capability_notes": notes,
+            "writer_deployment_checks": _deployment_checks(),
+            "findings": findings,
+        }
+
+    raw_write = design.get("write")
+    write = raw_write if isinstance(raw_write, dict) else {}
+    primary_operation = str(write.get("operation", "UNKNOWN")).upper()
+    if primary_operation not in WRITE_OPERATIONS:
+        primary_operation = "UNKNOWN"
+    normalized: dict[str, Any] = {
+        "contract_schema": WRITER_CONTRACT_SCHEMA_VERSION,
+        "primary_writer_id": normalized_primary_id,
+        "primary_writer_facts": {
+            "engine": "FLINK",
+            "engine_version": design.get("flink_version", "UNKNOWN"),
+            "hudi_version": design.get("hudi_version", "UNKNOWN"),
+            "operation": primary_operation,
+        },
+        "additional_writers": sorted(
+            normalized_writers, key=lambda writer: writer["writer_id"]
+        ),
+        "ownership": ownership,
+    }
+    if concurrency is not None:
+        normalized["observed_concurrency"] = concurrency
+
+    conflict = evidence_conflict
+    if conflicting_lock_evidence:
+        _add_finding(
+            findings,
+            "FLINK_WRITER_CONTRACT_CONFLICT",
+            "Observed lock configuration is conflicting across writers",
+        )
+        conflict = True
+    if concurrency is not None:
+        contract_conflict, contract_unsupported = _assess_contract_concurrency(
+            concurrency, findings, notes
+        )
+        conflict = conflict or contract_conflict
+        if contract_unsupported:
+            capability = "UNSUPPORTED"
+
+    if any(writer["engine"] != "FLINK" for writer in normalized_writers):
+        _add_note(
+            notes,
+            "FLINK_CROSS_ENGINE_WRITER_UNVERIFIED",
+            "Cross-engine writer compatibility is not certified by this 
assessment.",
+        )
+    if any(
+        writer["role"] in {"STANDALONE_COMPACTION", "STANDALONE_CLUSTERING"}
+        for writer in normalized_writers
+    ):
+        _add_note(
+            notes,
+            "FLINK_STANDALONE_TABLE_SERVICE_UNVERIFIED",
+            "Standalone table-service writers require deployment review.",
+        )
+    if any(
+        writer["hudi_version"] != design.get("hudi_version")
+        for writer in normalized_writers
+    ):
+        _add_finding(
+            findings,
+            "FLINK_VERSION_UNVERIFIED",
+            "Writer Hudi versions are not uniformly pinned to the design 
baseline",
+        )
+    if any(
+        writer["engine"] == "FLINK"
+        and writer["engine_version"] != design.get("flink_version")
+        for writer in normalized_writers
+    ):
+        _add_finding(
+            findings,
+            "FLINK_VERSION_UNVERIFIED",
+            "Flink writer versions are not uniformly pinned to the design 
baseline",
+        )
+
+    conflict = (
+        _evidence_conflicts(
+            normalized,
+            supplied_evidence,
+            supplied_conflicts,
+            findings,
+        )
+        or conflict
+    )
+    return {
+        "writer_contract_result": "CONFLICT" if conflict else "COMPLETE",
+        "multi_writer_capability": capability,
+        "normalized_writer_contract": normalized,
+        "writer_capability_notes": notes,
+        "writer_deployment_checks": _deployment_checks(),
+        "findings": findings,
+    }
diff --git 
a/hudi-ai-operator/skills/hudi-architect/references/flink-1.20-hudi-1.2.0-capabilities.md
 
b/hudi-ai-operator/skills/hudi-architect/references/flink-1.20-hudi-1.2.0-capabilities.md
index 26ac22defe8a..afef45c9dc80 100644
--- 
a/hudi-ai-operator/skills/hudi-architect/references/flink-1.20-hudi-1.2.0-capabilities.md
+++ 
b/hudi-ai-operator/skills/hudi-architect/references/flink-1.20-hudi-1.2.0-capabilities.md
@@ -71,7 +71,10 @@ the current checkout contains it.
 | Existing-table schema evolution or version upgrade | `REVIEW_REQUIRED`; no 
commands emitted |
 | Separate-writer or cross-engine takeover | `REVIEW_REQUIRED` |
 | Confirmed single-writer detection | Supported |
-| Multi-writer or unknown writer model | `REVIEW_REQUIRED`; deferred to PR7 |
+| Unknown writer model | `REVIEW_REQUIRED`; no contract is fabricated |
+| Multi-writer Writer Contract | Assessment-only through PR7; never executable 
|
+| OCC multi-writer capability | `UNVERIFIED`; deployment checks required |
+| NBCC on schemas 1–3 | `BLOCKED`; current COW/FLINK_STATE paths do not meet 
the simple-bucket requirement |
 | External-catalog requirement detection | Supported |
 | Five direct SQL / DFS Catalog / HMS Catalog profiles | Supported for new 
tables through PR6 |
 | Direct SQL or DFS Catalog with sink-owned HMS sync | Supported |
@@ -150,6 +153,30 @@ embedded modes, checks DFS path derivation and external 
HMS posture, exercises D
 an insert plan, and verifies Hive sync/naming defaults. It does not start HMS 
or Glue. Live
 connectivity and visibility are deployment checks rather than static status 
findings.
 
+## Implemented PR7 multi-writer Writer Contract
+
+PR7 adds Writer Contract schema 1 alongside design schemas 1, 2, and 3. It 
records the primary
+Flink writer, additional ingestion/backfill/cleanup or standalone-service 
writers, engine and Hudi
+versions, canonical write operations, and schema/table-configuration 
ownership. Primary engine,
+version, and operation facts come from the existing design contract rather 
than duplicate inputs.
+
+Observed concurrency mode, lock-provider class, and cross-writer lock 
consistency are optional
+facts, not configuration recommendations. The pinned `WriteConcurrencyMode` 
source recognizes OCC
+and NBCC as multi-writer modes, and `HoodieWriteConfig` makes LAZY 
failed-write handling effective
+for them. These static facts do not exercise concurrent commits, a lock 
service, or recovery, so
+OCC remains `UNVERIFIED`.
+
+Pinned Hudi 1.2.0 requires NBCC on MOR with simple bucket index (or the 
metadata table). Schemas 1
+and 2 are COW and schema 3 uses global `FLINK_STATE`, so PR7 reports those 
combinations
+`UNSUPPORTED` and blocks them without changing the index. Cross-engine and 
standalone-service
+contracts remain review-required.
+
+The PR5 evidence envelope may additionally report active writer IDs, 
schema-evolution owner,
+concurrency mode, lock provider, and lock consistency. Conflicting sources 
remain scoped and
+provenance-bearing; they produce an unverified reconciliation and Writer 
Contract `CONFLICT`, not
+an inferred migration. `COMPLETE` means only that the bounded inventory is 
present. PR7 never
+returns `SUPPORTED`, never emits multi-writer SQL, and never makes executable 
eligibility true.
+
 ## Implemented PR5 existing-table reconciliation contract
 
 PR5 reuses the explicit schema-1, schema-2, or schema-3 desired design and 
changes only the table
diff --git 
a/hudi-ai-operator/skills/hudi-architect/references/flink-1.20-hudi-1.2.0-capabilities.toml
 
b/hudi-ai-operator/skills/hudi-architect/references/flink-1.20-hudi-1.2.0-capabilities.toml
index 2bbfa5913a98..ac529449f421 100644
--- 
a/hudi-ai-operator/skills/hudi-architect/references/flink-1.20-hudi-1.2.0-capabilities.toml
+++ 
b/hudi-ai-operator/skills/hudi-architect/references/flink-1.20-hudi-1.2.0-capabilities.toml
@@ -13,9 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-schema_version = 6
+schema_version = 7
 baseline_id = "hudi-1.2.0-flink-1.20"
-validation_surface = 
"flink-sql-sink-existing-table-reconciliation-and-catalog-composition"
+validation_surface = 
"flink-sql-sink-existing-table-reconciliation-catalog-composition-and-multi-writer-contract"
 
 [hudi]
 version = "1.2.0"
@@ -125,6 +125,68 @@ FLINK_EXISTING_CATALOG_MUTATION_UNVERIFIED = 
"REVIEW_REQUIRED"
 FLINK_GLUE_CATALOG_UNVERIFIED = "REVIEW_REQUIRED"
 FLINK_MOR_CATALOG_NAMING_UNVERIFIED = "REVIEW_REQUIRED"
 
+[multi_writer_contract]
+contract_schema = 1
+supported_design_contract_schemas = [1, 2, 3]
+assessment_only = true
+executable_sql = false
+writer_contract_results = ["NOT_APPLICABLE", "COMPLETE", "INCOMPLETE", 
"CONFLICT"]
+capability_results = ["NOT_APPLICABLE", "UNVERIFIED", "UNSUPPORTED"]
+roles = [
+  "INGESTION",
+  "BACKFILL",
+  "CLEANUP",
+  "STANDALONE_COMPACTION",
+  "STANDALONE_CLUSTERING",
+  "OTHER",
+]
+relationships = ["INDEPENDENT_CONCURRENT", "MAY_OVERLAP"]
+engines = ["FLINK", "SPARK", "OTHER", "UNKNOWN"]
+operations = [
+  "INSERT",
+  "INSERT_PREPPED",
+  "UPSERT",
+  "UPSERT_PREPPED",
+  "BULK_INSERT",
+  "BULK_INSERT_PREPPED",
+  "DELETE",
+  "DELETE_PREPPED",
+  "BOOTSTRAP",
+  "INSERT_OVERWRITE",
+  "BUCKET_RESCALE",
+  "CLUSTER",
+  "DELETE_PARTITION",
+  "INSERT_OVERWRITE_TABLE",
+  "COMPACT",
+  "INDEX",
+  "ALTER_SCHEMA",
+  "LOG_COMPACT",
+  "UNKNOWN",
+]
+concurrency_modes = [
+  "SINGLE_WRITER",
+  "OPTIMISTIC_CONCURRENCY_CONTROL",
+  "NON_BLOCKING_CONCURRENCY_CONTROL",
+  "UNKNOWN",
+]
+lock_configuration_consistency = ["CONSISTENT", "CONFLICTING", "UNKNOWN"]
+deployment_checks = [
+  "verify_concurrent_writer_inventory",
+  "verify_uniform_concurrency_mode",
+  "verify_lock_provider_configuration",
+  "verify_lazy_failed_writes_cleaner_policy",
+  "verify_writer_recovery_procedure",
+  "verify_schema_evolution_ownership",
+]
+
+[multi_writer_contract.finding_status]
+FLINK_MULTI_WRITER_REVIEW = "REVIEW_REQUIRED"
+FLINK_WRITER_COMBINATION_UNSUPPORTED = "BLOCKED"
+FLINK_WRITER_CONTRACT_CONFLICT = "REVIEW_REQUIRED"
+FLINK_WRITER_CONTRACT_INVALID = "INCOMPLETE"
+FLINK_WRITER_CONTRACT_REQUIRED = "INCOMPLETE"
+FLINK_WRITER_OWNERSHIP_UNRESOLVED = "REVIEW_REQUIRED"
+
 [[catalog_profiles]]
 id = "direct_sql_none"
 resolution_mode = "direct_sql"
@@ -220,7 +282,12 @@ required_identity_facts = ["table_path"]
 required_schema_facts = ["columns"]
 required_timeline_facts = ["pending_instants"]
 required_writer_inventory_facts = ["concurrent_writers"]
-conditional_writer_inventory_facts = ["previous_writer_engine", 
"previous_writer_stopped"]
+conditional_writer_inventory_facts = [
+  "active_writers",
+  "previous_writer_engine",
+  "previous_writer_stopped",
+  "schema_evolution_owner_id",
+]
 comparison_fields = [
   "schema.columns",
   "table_config.base_file_format",
@@ -246,11 +313,16 @@ comparison_fields = [
   "writer_config.index_global",
   "writer_config.index_state_ttl_days",
   "writer_config.index_type",
+  "writer_config.lock_configuration_consistency",
+  "writer_config.lock_provider_class",
   "writer_config.operation",
   "writer_config.timestamp_key_generator",
+  "writer_config.write_concurrency_mode",
+  "writer_inventory.active_writers",
   "writer_inventory.concurrent_writers",
   "writer_inventory.previous_writer_engine",
   "writer_inventory.previous_writer_stopped",
+  "writer_inventory.schema_evolution_owner_id",
 ]
 comparison_results = ["CONFLICT", "MATCH", "NOT_APPLICABLE", "UNVERIFIED"]
 reconciliation_results = ["COMPATIBLE", "CONFLICT", "UNVERIFIED"]
@@ -346,6 +418,24 @@ path = 
"hudi-common/src/main/java/org/apache/hudi/common/model/HoodieRecord.java
 sha256 = "11587947231d5a5727fea25169c986a20a3079d7f62787d3efe44c1f6e3f066e"
 purpose = "Canonical Hudi metadata field names reserved by the write path"
 
+[[source_anchors]]
+id = "write_concurrency_mode"
+path = 
"hudi-common/src/main/java/org/apache/hudi/common/model/WriteConcurrencyMode.java"
+sha256 = "13e8e5b91401c9a9e68d98cd19c2a44d450e691396c2ded4541b5479dd62b225"
+purpose = "Single-writer, optimistic, and non-blocking concurrency mode 
semantics"
+
+[[source_anchors]]
+id = "write_operation_type"
+path = 
"hudi-common/src/main/java/org/apache/hudi/common/model/WriteOperationType.java"
+sha256 = "0cced364652815143b7265c8ccd7ea9a6db87f9c4f9be28b05923adda9132c68"
+purpose = "Canonical Hudi 1.2.0 write operation names"
+
+[[source_anchors]]
+id = "hoodie_write_config"
+path = 
"hudi-client/hudi-client-common/src/main/java/org/apache/hudi/config/HoodieWriteConfig.java"
+sha256 = "359d69bcf5c8c442923d81544c15bec4e5ade91afca3dc71043271d1d2ee4d8c"
+purpose = "Multi-writer cleaner policy and non-blocking concurrency 
constraints"
+
 [[source_anchors]]
 id = "hoodie_table_config"
 path = 
"hudi-common/src/main/java/org/apache/hudi/common/table/HoodieTableConfig.java"
@@ -500,7 +590,7 @@ purpose = "Global-index record routing and partition-change 
behavior"
 id = "streamer_util"
 path = 
"hudi-flink-datasource/hudi-flink/src/main/java/org/apache/hudi/util/StreamerUtil.java"
 sha256 = "c771e137ebcd45c94a941857a246dc741e1dde2c0e0158900fd48fe8884f83e2"
-purpose = "Write-side record merge mode inference and table-property 
persistence"
+purpose = "Flink lock-provider resolution, record merge mode inference, and 
table-property persistence"
 
 # Catalog option types are tracked separately from table connector options.
 [[catalog_option_types]]
@@ -970,3 +1060,15 @@ id = "FLINK_MOR_CATALOG_NAMING_UNVERIFIED"
 requirement = "Pin MOR Hive registrations to RO, RT, or ALL names derived from 
query modes."
 python_test = "test_pr6_rejects_mor_catalog_naming_drift"
 java_test = "testPinnedMorHiveSyncNamingDefaults"
+
+[[implemented_acceptance_checks]]
+id = "FLINK_MULTI_WRITER_REVIEW"
+requirement = "Keep recognized OCC and NBCC multi-writer modes assessment-only 
and require the pinned LAZY failed-writes policy."
+python_test = "test_pr7_complete_occ_contract_is_assessment_only"
+java_test = "testPinnedMultiWriterModesRequireLazyFailedWritesPolicy"
+
+[[implemented_acceptance_checks]]
+id = "FLINK_WRITER_COMBINATION_UNSUPPORTED"
+requirement = "Reject NBCC for the existing COW and FLINK_STATE-indexed MOR 
paths because Hudi 1.2.0 requires MOR with simple bucket index."
+python_test = "test_pr7_nbcc_is_unsupported_for_existing_bounded_indexes"
+java_test = "testPinnedNbccRejectsCowAndNonSimpleBucketIndex"
diff --git 
a/hudi-ai-operator/skills/hudi-architect/references/flink-config-templates.md 
b/hudi-ai-operator/skills/hudi-architect/references/flink-config-templates.md
index 4b36cec91bd0..d64273fb157e 100644
--- 
a/hudi-ai-operator/skills/hudi-architect/references/flink-config-templates.md
+++ 
b/hudi-ai-operator/skills/hudi-architect/references/flink-config-templates.md
@@ -30,8 +30,9 @@ Verified baseline: Apache Hudi 1.2.0 / Apache Flink 1.20
 Baseline ID: hudi-1.2.0-flink-1.20
 Hudi source revision: f05c83f2b97732de7a558ff9b26959e1139c05f5
 Fixture version: Apache Flink 1.20.1
-Capability manifest schema: 6
+Capability manifest schema: 7
 Catalog contract schema: 1
+Writer contract schema: 1
 Catalog profile: <one pinned profile or unresolved>
 Design contract schema: <1 append-only COW | 2 mutable COW | 3 mutable MOR>
 
@@ -65,8 +66,9 @@ Use only the machine result from 
`validate_flink_existing_table.py`:
 Flink design status: <CONFIG_VALIDATED | INCOMPLETE | BLOCKED | 
REVIEW_REQUIRED>
 Reconciliation result: <COMPATIBLE | CONFLICT | UNVERIFIED>
 Verified baseline: Apache Hudi 1.2.0 / Apache Flink 1.20
-Capability manifest schema: 6
+Capability manifest schema: 7
 Catalog contract schema: 1
+Writer contract schema: 1
 Catalog profile: <direct_sql_none or requested non-legacy profile>
 Catalog setup SQL: <always empty for an existing table>
 Effective catalog configuration: <validator-produced mapping>
@@ -74,6 +76,8 @@ Reconciliation schema: 1
 Desired design contract schema: <1 | 2 | 3>
 Writer transition: <same_job_restore | cold_start_new_job | 
flink_writer_takeover |
 cross_engine_takeover | unknown>
+Writer contract result: <NOT_APPLICABLE | COMPLETE | INCOMPLETE | CONFLICT>
+Multi-writer capability: <NOT_APPLICABLE | UNVERIFIED | UNSUPPORTED>
 
 Evidence:
 - <evidence ID, kind, table path, in-scope flag, observation time, and digest; 
no raw secrets>
@@ -92,6 +96,8 @@ Deployment checks:
 - For a mutable cold start, verify index bootstrap before relying on the first 
checkpoint.
 - Retain the validator-produced catalog deployment checks for any non-legacy 
profile; these are
   observations to perform, not permission to mutate an existing registration.
+- Retain Writer Contract checks when applicable; they do not establish live 
lock or recovery
+  behavior.
 
 Executable output:
 - Withheld because PR5 is assessment-only, including for COMPATIBLE results.
@@ -112,8 +118,9 @@ Verified baseline: Apache Hudi 1.2.0 / Apache Flink 1.20
 Baseline ID: hudi-1.2.0-flink-1.20
 Hudi source revision: f05c83f2b97732de7a558ff9b26959e1139c05f5
 Fixture version: Apache Flink 1.20.1
-Capability manifest schema: 6
+Capability manifest schema: 7
 Catalog contract schema: 1
+Writer contract schema: 1
 Catalog profile: <direct_sql_none | direct_sql_hive_metastore | 
hudi_dfs_catalog_none |
 hudi_dfs_catalog_hive_metastore | hudi_hms_catalog_hive_metastore>
 Design contract schema: <1 append-only COW | 2 mutable COW | 3 mutable MOR>
diff --git 
a/hudi-ai-operator/skills/hudi-architect/references/flink-decision-overrides.md 
b/hudi-ai-operator/skills/hudi-architect/references/flink-decision-overrides.md
index e17bcacfc70f..bfe27efde3ac 100644
--- 
a/hudi-ai-operator/skills/hudi-architect/references/flink-decision-overrides.md
+++ 
b/hudi-ai-operator/skills/hudi-architect/references/flink-decision-overrides.md
@@ -74,6 +74,11 @@ do not replace the shared table-design rules and do not form 
a standalone planne
 | Table lifecycle unknown | `FLINK_TABLE_LIFECYCLE_REQUIRED` | `INCOMPLETE` | 
No |
 | Independent writer exists | `FLINK_MULTI_WRITER_REVIEW` | `REVIEW_REQUIRED` 
| No |
 | Writer model unknown | `FLINK_WRITER_MODEL_UNRESOLVED` | `REVIEW_REQUIRED` | 
No |
+| Confirmed multi-writer topology has no Writer Contract | 
`FLINK_WRITER_CONTRACT_REQUIRED` | `INCOMPLETE` | No |
+| Writer Contract is malformed or contains unsupported fields | 
`FLINK_WRITER_CONTRACT_INVALID` | `INCOMPLETE` | No |
+| Schema or table-configuration ownership is unresolved | 
`FLINK_WRITER_OWNERSHIP_UNRESOLVED` | `REVIEW_REQUIRED` | No |
+| Writer Contract facts conflict internally or with scoped evidence | 
`FLINK_WRITER_CONTRACT_CONFLICT` | `REVIEW_REQUIRED` | No |
+| Observed concurrency combination is incompatible with the existing bounded 
design | `FLINK_WRITER_COMBINATION_UNSUPPORTED` | `BLOCKED` | No |
 | External catalog required | `FLINK_EXTERNAL_CATALOG_REVIEW` | 
`REVIEW_REQUIRED` | No |
 | Catalog requirement unknown | `FLINK_CATALOG_REQUIREMENT_UNRESOLVED` | 
`REVIEW_REQUIRED` | No |
 | Physical schema missing, insufficient, or available only through an 
unreadable pointer | `FLINK_PHYSICAL_SCHEMA_REQUIRED` | `INCOMPLETE` | No |
@@ -261,7 +266,7 @@ to the executable contract just like `F10_SAFE_APPEND`.
   compaction-operation execution are enabled with `num_commits` and five delta 
commits. Five is a
   fixed Hudi 1.2.0 effective default, not a tuned SLA.
 - A standalone compactor is an independent writer and remains 
`REVIEW_REQUIRED`; concurrency
-  configuration is deferred to PR7. Unknown ownership also withholds SQL.
+  configuration is not emitted by the PR7 Writer Contract. Unknown ownership 
also withholds SQL.
 - Snapshot readers see base plus log state at a visible completed instant. 
Read-optimized readers
   see compacted base-file state and advance only after compaction. Reader 
`query.type` is not a
   sink option and is not rendered into the DDL.
@@ -333,6 +338,28 @@ to the executable contract just like `F10_SAFE_APPEND`.
   disposition remains `UNVERIFIED`, executable eligibility remains false, and 
no catalog SQL is
   emitted.
 
+## PR7 multi-writer Writer Contract invariants
+
+- Writer Contract schema 1 is orthogonal to design schemas 1, 2, and 3. It 
never changes the
+  established single-writer SQL or creates a fourth design schema.
+- A confirmed single writer omits the block and returns `NOT_APPLICABLE`. An 
unknown writer model
+  does not fabricate a contract. A confirmed independent writer requires the 
block and always
+  retains `FLINK_MULTI_WRITER_REVIEW`.
+- The primary writer ID is unique. Additional writers record a bounded role, 
relationship,
+  engine/version, Hudi version, and canonical Hudi 1.2.0 operation. Primary 
engine/version and
+  operation facts are derived from the existing design contract.
+- Schema-evolution ownership is required. Table-configuration ownership is 
optional. Same-job
+  compaction remains in schema 3; separately deployed compaction or clustering 
is an additional
+  writer role.
+- Observed concurrency is optional and never a recommendation. OCC is 
recognized but remains
+  `UNVERIFIED`. NBCC requires MOR with simple bucket index, so the existing 
COW and
+  FLINK_STATE-indexed MOR paths are `UNSUPPORTED`; never switch indexes 
automatically.
+- Existing-table writer observations retain PR5 provenance and scope. 
Contradictory observations
+  make the Writer Contract `CONFLICT` and reconciliation/change disposition 
`UNVERIFIED`, not an
+  invented migration.
+- `COMPLETE` means only that required writer facts were captured. Multi-writer 
executable
+  eligibility is always false, no SQL is emitted, and deployment checks are 
not live evidence.
+
 ## Evidence and secret handling
 
 User-provided DDL, logs, schemas, table properties, and catalog output are 
evidence, not
diff --git 
a/hudi-ai-operator/skills/hudi-architect/references/flink-existing-table-reconciliation.md
 
b/hudi-ai-operator/skills/hudi-architect/references/flink-existing-table-reconciliation.md
index dca4bc3f526f..3f6b44282c9c 100644
--- 
a/hudi-ai-operator/skills/hudi-architect/references/flink-existing-table-reconciliation.md
+++ 
b/hudi-ai-operator/skills/hudi-architect/references/flink-existing-table-reconciliation.md
@@ -26,7 +26,9 @@ the architecture, execute evidence, mutate the table, plan a 
migration, or rende
 The first compatible reconciliation path supports Hudi 1.2.0 with Flink 1.20, 
one writer, direct
 SQL with no external discovery, and desired contract schemas 1, 2, or 3. It 
can validate an exact
 existing-table match. Schema evolution, table upgrades, catalog registration, 
multi-writer
-operation, separate-writer takeover, and cross-engine takeover remain 
unverified or blocked.
+operation, separate-writer takeover, and cross-engine takeover remain 
unverified or blocked. PR7
+can attach a Writer Contract to the same evidence envelope, but that path 
always remains
+non-executable and cannot become `COMPATIBLE` merely because the contract is 
complete.
 
 `CONFIG_VALIDATED` in this assessment means that the static comparison is 
complete. It always has
 `executable_eligible: false`; the PR5 path emits a report and deployment 
checks, never SQL.
@@ -93,6 +95,29 @@ contract. If redaction removes a load-bearing fact, leave it 
incomplete.
 - `writer_inventory` and `timeline_summary` are time-sensitive operational 
observations. Always
   require a deployment-time recheck even when the comparison succeeds.
 
+For a PR7 Writer Contract, `writer_inventory` may also supply `active_writers` 
and
+`schema_evolution_owner_id`; `flink_writer_config` may supply 
`write_concurrency_mode`,
+`lock_provider_class`, and `lock_configuration_consistency`. These are 
optional observations and
+retain the same evidence scope and provenance. If sources disagree, or an 
observation contradicts
+the explicit Writer Contract, report `FLINK_EXISTING_EVIDENCE_CONFLICT` and/or
+`FLINK_WRITER_CONTRACT_CONFLICT`, keep reconciliation and change disposition 
`UNVERIFIED`, and do
+not infer migration work.
+
+Evaluate supplied concurrency observations even when the optional Writer 
Contract concurrency
+block is absent. Retain them as provenance-bearing `UNVERIFIED` comparisons 
without promoting
+them into the normalized contract. Continue the capability assessment when the 
contract itself is
+missing or invalid, so structural incompleteness cannot hide an unsupported 
bounded combination.
+Before accepting the single-writer branch, also reconcile the active-writer 
inventory: more than
+one active writer contradicts the desired topology. Treat that snapshot as 
internally inconsistent
+with `concurrent_writers: false` only when both facts share an evidence source.
+
+For a declared multi-writer topology, an active-writer snapshot may contain 
any subset of declared
+writer IDs; inactive declared writers are not a mismatch. An undeclared active 
writer remains a
+Writer Contract conflict. `concurrent_writers` is an operational observation, 
not proof that every
+declared writer is active, so do not require it to be `true` merely because 
the desired topology is
+multi-writer. General deployment checks require `confirm_single_writer` only 
for single-writer
+designs.
+
 Do not silently merge contradictory sources. Preserve provenance in each 
comparison item.
 
 ## Writer transition
diff --git 
a/hudi-ai-operator/skills/hudi-architect/references/flink-question-flow.md 
b/hudi-ai-operator/skills/hudi-architect/references/flink-question-flow.md
index c923c7b6c78d..d22894bcc9ab 100644
--- a/hudi-ai-operator/skills/hudi-architect/references/flink-question-flow.md
+++ b/hudi-ai-operator/skills/hudi-architect/references/flink-question-flow.md
@@ -84,11 +84,15 @@ An asynchronous table service that runs inside and is 
coordinated by the same Fl
 independent writer. A separately deployed compactor or clustering job is.
 
 - Confirmed single writer → record `SINGLE_WRITER` as a confirmed fact and 
continue.
-- Another independent writer → `REVIEW_REQUIRED` with 
`FLINK_MULTI_WRITER_REVIEW`.
+- Another independent writer → load `flink-writer-contract.md`, collect the 
version-1 Writer
+  Contract, and retain `REVIEW_REQUIRED` with `FLINK_MULTI_WRITER_REVIEW` even 
when the contract is
+  complete.
 - Not sure → `REVIEW_REQUIRED` with `FLINK_WRITER_MODEL_UNRESOLVED`.
 
+For a confirmed independent writer, collect identity, role, relationship, 
engine/version, Hudi
+version, operation, and ownership. Collect concurrency mode and lock class 
only as observed facts.
 Do not recommend OCC, NBCC, a lock provider, or an index/concurrency 
combination on this bounded
-path.
+path, and do not create a contract while the writer model is unknown.
 
 ## F3 — External catalog visibility
 
@@ -402,7 +406,7 @@ the report and deployment checks with executable 
eligibility `false`, and never
 known table-property or schema conflict is `CONFLICT`; incomplete, 
contradictory, evolutionary,
 or unsupported evidence is `UNVERIFIED`.
 
-## PR2/PR3/PR4/PR5/PR6 validation and completion
+## PR2/PR3/PR4/PR5/PR6/PR7 validation and completion
 
 After all applicable questions, build a version-1 JSON contract for 
append-only COW, a version-2
 contract for mutable COW, or a version-3 contract for mutable MOR. All contain 
confirmed safety
@@ -420,6 +424,14 @@ existing table, a non-legacy block is accepted only to 
report
 `FLINK_EXISTING_CATALOG_MUTATION_UNVERIFIED`; do not collect a new evidence 
kind or infer that
 registration already exists.
 
+When F2 confirms an independent writer, add Writer Contract schema 1 without 
changing design
+schema 1, 2, or 3. The contract records writer inventory, 
schema/table-configuration ownership,
+and optional observed concurrency facts. Run the same validator, retain
+`writer_contract_result`, `multi_writer_capability`, normalized facts, notes, 
and deployment
+checks, and withhold all SQL. A complete contract is assessment completeness, 
not an executable
+support claim. OCC remains `UNVERIFIED`; NBCC is `UNSUPPORTED` for the current 
COW and
+FLINK_STATE-indexed MOR paths.
+
 For a new table, run:
 
 ```bash
@@ -437,5 +449,6 @@ Executable eligible: true
 ```
 
 Any validation failure withholds all SQL and reports executable eligibility as 
`false`. Consult
-`flink-decision-overrides.md` for precedence, `flink-catalog-composition.md` 
for PR6 profiles, and
-`flink-config-templates.md` for both envelopes.
+`flink-decision-overrides.md` for precedence, `flink-catalog-composition.md` 
for PR6 profiles,
+`flink-writer-contract.md` for PR7 assessment rules, and 
`flink-config-templates.md` for both
+envelopes.
diff --git 
a/hudi-ai-operator/skills/hudi-architect/references/flink-warnings.md 
b/hudi-ai-operator/skills/hudi-architect/references/flink-warnings.md
index 210408fe2448..db9018e7ea5a 100644
--- a/hudi-ai-operator/skills/hudi-architect/references/flink-warnings.md
+++ b/hudi-ai-operator/skills/hudi-architect/references/flink-warnings.md
@@ -194,10 +194,11 @@ The table lifecycle must be known before choosing a path.
 **Trigger:** Another pipeline, backfill, cleanup writer, cross-engine writer, 
standalone compactor,
 or standalone clustering job can commit to the table.
 
-**Message:** The single-writer assumptions are invalid. This path does not 
claim an OCC, NBCC,
-lock-provider, index, or table-service combination is safe.
+**Message:** The single-writer assumptions are invalid. PR7 can record a 
Writer Contract, but it
+does not claim an OCC, NBCC, lock-provider, index, or table-service 
combination is safe.
 
-**Action:** `REVIEW_REQUIRED`; withhold concurrency-sensitive configuration.
+**Action:** `REVIEW_REQUIRED`; collect the Writer Contract and withhold 
concurrency-sensitive
+configuration and executable output.
 
 ## FLINK_WRITER_MODEL_UNRESOLVED
 
@@ -209,6 +210,49 @@ be treated as safe.
 
 **Action:** `REVIEW_REQUIRED`; do not infer `SINGLE_WRITER` or recommend 
concurrency settings.
 
+## FLINK_WRITER_CONTRACT_REQUIRED
+
+**Trigger:** The writer model is confirmed multi-writer but no version-1 
Writer Contract exists.
+
+**Message:** The writer inventory and ownership facts are incomplete.
+
+**Action:** `INCOMPLETE`; collect the bounded PR7 contract without selecting 
concurrency settings.
+
+## FLINK_WRITER_CONTRACT_INVALID
+
+**Trigger:** A Writer Contract has an invalid type, unknown field, duplicate 
writer ID, invalid
+enum, undeclared owner, or another structural error.
+
+**Message:** The validator cannot normalize the writer topology without 
guessing.
+
+**Action:** `INCOMPLETE`; correct the explicit facts and emit no executable 
output.
+
+## FLINK_WRITER_OWNERSHIP_UNRESOLVED
+
+**Trigger:** Schema-evolution or supplied table-configuration ownership is 
explicitly `UNKNOWN`.
+
+**Message:** Concurrent schema or configuration changes have no confirmed 
owner.
+
+**Action:** `REVIEW_REQUIRED`; establish ownership before deployment.
+
+## FLINK_WRITER_CONTRACT_CONFLICT
+
+**Trigger:** The contract contradicts the writer model or scoped 
existing-table evidence, or the
+observed lock configuration is conflicting.
+
+**Message:** PR7 preserves both facts and does not select one as authoritative.
+
+**Action:** `REVIEW_REQUIRED`; return `CONFLICT`, withhold SQL, and reconcile 
the evidence.
+
+## FLINK_WRITER_COMBINATION_UNSUPPORTED
+
+**Trigger:** NBCC is observed for design schema 1 or 2 COW, or schema 3 MOR 
with `FLINK_STATE`.
+
+**Message:** Pinned Hudi 1.2.0 requires NBCC on MOR with simple bucket index 
(or the metadata
+table), which none of the existing executable schemas uses.
+
+**Action:** `BLOCKED`; do not switch the table type or index automatically.
+
 ## FLINK_EXTERNAL_CATALOG_REVIEW
 
 **Trigger:** An external engine or BI tool must discover the table through a 
catalog or metastore.
diff --git 
a/hudi-ai-operator/skills/hudi-architect/references/flink-writer-contract.md 
b/hudi-ai-operator/skills/hudi-architect/references/flink-writer-contract.md
new file mode 100644
index 000000000000..e03113a88546
--- /dev/null
+++ b/hudi-ai-operator/skills/hudi-architect/references/flink-writer-contract.md
@@ -0,0 +1,105 @@
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+# Flink multi-writer Writer Contract
+
+Use this reference only after F2 confirms an independent writer or separately 
deployed table
+service. The contract is orthogonal to design schemas 1, 2, and 3. It records 
the writer topology
+and assesses pinned Hudi 1.2.0 constraints; it never selects OCC, NBCC, a lock 
provider, an index,
+or a recovery strategy, and it never makes multi-writer SQL executable.
+
+## Contract shape
+
+Set `writer_contract.contract_schema` to `1`. Record one unique 
`primary_writer_id` for the Flink
+design and at least one `additional_writers` entry. Each additional writer 
requires:
+
+- a unique `writer_id`;
+- role: `INGESTION`, `BACKFILL`, `CLEANUP`, `STANDALONE_COMPACTION`,
+  `STANDALONE_CLUSTERING`, or `OTHER`;
+- relationship: `INDEPENDENT_CONCURRENT` or `MAY_OVERLAP`;
+- engine: `FLINK`, `SPARK`, `OTHER`, or `UNKNOWN`;
+- concrete engine and Hudi version strings, using the literal `UNKNOWN` only 
when the fact has
+  explicitly been established as unknown; and
+- a canonical Hudi 1.2.0 `WriteOperationType` name, or `UNKNOWN`.
+
+Do not duplicate primary engine, engine version, Hudi version, or operation in 
the input block.
+The validator derives those facts from the existing Flink design contract.
+
+`ownership.schema_evolution_owner_id` is required and must reference a 
declared writer,
+`EXTERNAL_CONTROL_PLANE`, or `UNKNOWN`. `table_configuration_owner_id` is 
optional with the same
+reference rule. `UNKNOWN` ownership produces 
`FLINK_WRITER_OWNERSHIP_UNRESOLVED`.
+
+Express a standalone compactor or clustering service as an additional writer 
with the matching
+role. Same-job asynchronous compaction remains part of design schema 3 and is 
not an independent
+writer.
+
+## Observed concurrency
+
+`observed_concurrency` is optional and observation-only. When supplied, it 
requires:
+
+- `mode`: `SINGLE_WRITER`, `OPTIMISTIC_CONCURRENCY_CONTROL`,
+  `NON_BLOCKING_CONCURRENCY_CONTROL`, or `UNKNOWN`;
+- the observed lock-provider class name; and
+- `lock_configuration_consistency`: `CONSISTENT`, `CONFLICTING`, or `UNKNOWN`.
+
+Never ask the user to choose a mode or provider to make validation pass. Do 
not collect provider
+credentials, timeout/retry tuning, namespaces, conflict rates, keyspace 
assumptions, or file-group
+overlap guesses.
+
+The pinned source recognizes OCC and NBCC as multi-writer modes and requires 
an effective LAZY
+failed-writes policy. PR7 still reports OCC as `UNVERIFIED`: its fixtures do 
not run concurrent
+writers, a lock service, or recovery. NBCC requires MOR with simple bucket 
index (or the metadata
+table). The existing schema-1/2 COW paths and schema-3 MOR path with 
`FLINK_STATE` therefore return
+`UNSUPPORTED` and `FLINK_WRITER_COMBINATION_UNSUPPORTED`. Never switch the 
index automatically.
+
+## Existing-table evidence
+
+PR5 evidence may optionally add `active_writers` and 
`schema_evolution_owner_id` to
+`writer_inventory`, and `write_concurrency_mode`, `lock_provider_class`, and
+`lock_configuration_consistency` to `flink_writer_config`. Every item retains 
the existing
+evidence ID, timestamp, digest, and exact table-path scope.
+
+Only in-scope, non-conflicting observations contribute. Evidence disagreement 
or a mismatch with
+the explicit Writer Contract produces `FLINK_EXISTING_EVIDENCE_CONFLICT` and/or
+`FLINK_WRITER_CONTRACT_CONFLICT`. Keep the Writer Contract `CONFLICT`, 
reconciliation
+`UNVERIFIED`, change disposition `UNVERIFIED`, and executable eligibility 
`false`; do not infer a
+migration or silently replace either fact.
+
+Assess supplied concurrency evidence even when `observed_concurrency` is 
absent from the explicit
+contract. Preserve that evidence and its provenance as an unverified 
comparison; do not copy it
+into `normalized_writer_contract` or treat missing optional contract facts as 
inferred values.
+Apply the same independent capability assessment when the Writer Contract is 
missing or invalid:
+the contract remains `INCOMPLETE`, while bounded NBCC evidence still returns 
`UNSUPPORTED`.
+Reject concurrency modes and lock-consistency values outside the enumerations 
above.
+
+Treat `active_writers` as a time-sensitive snapshot, not as the complete 
declared topology. Every
+observed active writer must be declared, but a declared writer need not be 
active in that snapshot.
+Multiple active writers cannot validate a single-writer design. Report the 
combination of multiple
+active writers and `concurrent_writers: false` as internally inconsistent only 
when both facts come
+from the same evidence source; do not combine separate snapshots into a 
fabricated contradiction.
+
+## Result semantics
+
+Return `writer_contract_result` as `NOT_APPLICABLE`, `COMPLETE`, `INCOMPLETE`, 
or `CONFLICT` and
+`multi_writer_capability` as `NOT_APPLICABLE`, `UNVERIFIED`, or `UNSUPPORTED`. 
Include the
+normalized contract, capability notes, and deployment checks. Never return 
`SUPPORTED`.
+
+Deployment checks remind the operator to verify writer inventory, uniform 
concurrency mode, lock
+configuration, LAZY failed-write handling, recovery procedure, and schema 
ownership. They are not
+evidence that a deployment or service was inspected. The general 
existing-table checks include
+`confirm_single_writer` only for a single-writer design; a multi-writer design 
uses the bounded
+Writer Contract deployment checks instead.
diff --git 
a/hudi-ai-operator/skills/hudi-architect/validate_flink_capabilities.py 
b/hudi-ai-operator/skills/hudi-architect/validate_flink_capabilities.py
index cc28beee9221..46aefbe3d798 100644
--- a/hudi-ai-operator/skills/hudi-architect/validate_flink_capabilities.py
+++ b/hudi-ai-operator/skills/hudi-architect/validate_flink_capabilities.py
@@ -40,7 +40,7 @@ DEFAULT_MANIFEST = (
     SKILL_DIR / "references" / "flink-1.20-hudi-1.2.0-capabilities.toml"
 )
 
-EXPECTED_SCHEMA_VERSION = 6
+EXPECTED_SCHEMA_VERSION = 7
 EXPECTED_BASELINE_ID = "hudi-1.2.0-flink-1.20"
 EXPECTED_HUDI_VERSION = "1.2.0"
 EXPECTED_HUDI_SOURCE_REVISION = "f05c83f2b97732de7a558ff9b26959e1139c05f5"
@@ -333,6 +333,73 @@ EXPECTED_CATALOG_OPTION_TYPES = [
         "source_symbol": "TABLE_EXTERNAL",
     },
 ]
+EXPECTED_MULTI_WRITER_CONTRACT = {
+    "contract_schema": 1,
+    "supported_design_contract_schemas": [1, 2, 3],
+    "assessment_only": True,
+    "executable_sql": False,
+    "writer_contract_results": [
+        "NOT_APPLICABLE",
+        "COMPLETE",
+        "INCOMPLETE",
+        "CONFLICT",
+    ],
+    "capability_results": ["NOT_APPLICABLE", "UNVERIFIED", "UNSUPPORTED"],
+    "roles": [
+        "INGESTION",
+        "BACKFILL",
+        "CLEANUP",
+        "STANDALONE_COMPACTION",
+        "STANDALONE_CLUSTERING",
+        "OTHER",
+    ],
+    "relationships": ["INDEPENDENT_CONCURRENT", "MAY_OVERLAP"],
+    "engines": ["FLINK", "SPARK", "OTHER", "UNKNOWN"],
+    "operations": [
+        "INSERT",
+        "INSERT_PREPPED",
+        "UPSERT",
+        "UPSERT_PREPPED",
+        "BULK_INSERT",
+        "BULK_INSERT_PREPPED",
+        "DELETE",
+        "DELETE_PREPPED",
+        "BOOTSTRAP",
+        "INSERT_OVERWRITE",
+        "BUCKET_RESCALE",
+        "CLUSTER",
+        "DELETE_PARTITION",
+        "INSERT_OVERWRITE_TABLE",
+        "COMPACT",
+        "INDEX",
+        "ALTER_SCHEMA",
+        "LOG_COMPACT",
+        "UNKNOWN",
+    ],
+    "concurrency_modes": [
+        "SINGLE_WRITER",
+        "OPTIMISTIC_CONCURRENCY_CONTROL",
+        "NON_BLOCKING_CONCURRENCY_CONTROL",
+        "UNKNOWN",
+    ],
+    "lock_configuration_consistency": ["CONSISTENT", "CONFLICTING", "UNKNOWN"],
+    "deployment_checks": [
+        "verify_concurrent_writer_inventory",
+        "verify_uniform_concurrency_mode",
+        "verify_lock_provider_configuration",
+        "verify_lazy_failed_writes_cleaner_policy",
+        "verify_writer_recovery_procedure",
+        "verify_schema_evolution_ownership",
+    ],
+    "finding_status": {
+        "FLINK_MULTI_WRITER_REVIEW": "REVIEW_REQUIRED",
+        "FLINK_WRITER_COMBINATION_UNSUPPORTED": "BLOCKED",
+        "FLINK_WRITER_CONTRACT_CONFLICT": "REVIEW_REQUIRED",
+        "FLINK_WRITER_CONTRACT_INVALID": "INCOMPLETE",
+        "FLINK_WRITER_CONTRACT_REQUIRED": "INCOMPLETE",
+        "FLINK_WRITER_OWNERSHIP_UNRESOLVED": "REVIEW_REQUIRED",
+    },
+}
 EXPECTED_EXISTING_TABLE_RECONCILIATION_CONTRACT = {
     "reconciliation_schema": 1,
     "supported_design_contract_schemas": [1, 2, 3],
@@ -387,8 +454,10 @@ EXPECTED_EXISTING_TABLE_RECONCILIATION_CONTRACT = {
     "required_timeline_facts": ["pending_instants"],
     "required_writer_inventory_facts": ["concurrent_writers"],
     "conditional_writer_inventory_facts": [
+        "active_writers",
         "previous_writer_engine",
         "previous_writer_stopped",
+        "schema_evolution_owner_id",
     ],
     "comparison_fields": [
         "schema.columns",
@@ -415,11 +484,16 @@ EXPECTED_EXISTING_TABLE_RECONCILIATION_CONTRACT = {
         "writer_config.index_global",
         "writer_config.index_state_ttl_days",
         "writer_config.index_type",
+        "writer_config.lock_configuration_consistency",
+        "writer_config.lock_provider_class",
         "writer_config.operation",
         "writer_config.timestamp_key_generator",
+        "writer_config.write_concurrency_mode",
+        "writer_inventory.active_writers",
         "writer_inventory.concurrent_writers",
         "writer_inventory.previous_writer_engine",
         "writer_inventory.previous_writer_stopped",
+        "writer_inventory.schema_evolution_owner_id",
     ],
     "comparison_results": ["CONFLICT", "MATCH", "NOT_APPLICABLE", 
"UNVERIFIED"],
     "reconciliation_results": ["COMPATIBLE", "CONFLICT", "UNVERIFIED"],
@@ -534,6 +608,14 @@ EXPECTED_ACCEPTANCE_EVIDENCE = {
         "test_pr4_valid_mutable_mor_design_renders_exact_executable_sql",
         "testMutableMorSinkAndCompactionPlan",
     ),
+    "FLINK_MULTI_WRITER_REVIEW": (
+        "test_pr7_complete_occ_contract_is_assessment_only",
+        "testPinnedMultiWriterModesRequireLazyFailedWritesPolicy",
+    ),
+    "FLINK_WRITER_COMBINATION_UNSUPPORTED": (
+        "test_pr7_nbcc_is_unsupported_for_existing_bounded_indexes",
+        "testPinnedNbccRejectsCowAndNonSimpleBucketIndex",
+    ),
 }
 SHA1_PATTERN = re.compile(r"[0-9a-f]{40}")
 SHA256_PATTERN = re.compile(r"[0-9a-f]{64}")
@@ -555,7 +637,8 @@ def validate_manifest(manifest: dict[str, Any]) -> 
list[str]:
         "schema_version": EXPECTED_SCHEMA_VERSION,
         "baseline_id": EXPECTED_BASELINE_ID,
         "validation_surface": (
-            
"flink-sql-sink-existing-table-reconciliation-and-catalog-composition"
+            "flink-sql-sink-existing-table-reconciliation-catalog-composition-"
+            "and-multi-writer-contract"
         ),
     }
     for key, expected in expected_scalars.items():
@@ -791,6 +874,12 @@ def validate_manifest(manifest: dict[str, Any]) -> 
list[str]:
                     f"catalog_option_types[{index}] references unknown source 
anchors: {unknown}"
                 )
 
+    multi_writer_contract = manifest.get("multi_writer_contract")
+    if multi_writer_contract != EXPECTED_MULTI_WRITER_CONTRACT:
+        errors.append(
+            "multi_writer_contract does not match the bounded PR7 assessment 
surface"
+        )
+
     physical_types = manifest.get("physical_types")
     if not isinstance(physical_types, dict):
         errors.append("physical_types must be a table")
@@ -877,6 +966,7 @@ def validation_evidence(
         "catalog_contract_schema": manifest["catalog_composition_contract"][
             "contract_schema"
         ],
+        "writer_contract_schema": 
manifest["multi_writer_contract"]["contract_schema"],
         "catalog_profile": catalog_profile,
         "design_contract_schema": (
             manifest["executable_contract"]["contract_schema"]
diff --git a/hudi-ai-operator/skills/hudi-architect/validate_flink_design.py 
b/hudi-ai-operator/skills/hudi-architect/validate_flink_design.py
index 14310e851594..88f43649bc9a 100644
--- a/hudi-ai-operator/skills/hudi-architect/validate_flink_design.py
+++ b/hudi-ai-operator/skills/hudi-architect/validate_flink_design.py
@@ -33,6 +33,7 @@ from pathlib import Path
 from typing import Any, TypeGuard
 from urllib.parse import SplitResult, parse_qsl, unquote, urlsplit
 
+from flink_writer_contract import assess_writer_contract
 from validate_flink_capabilities import (
     DEFAULT_MANIFEST,
     load_manifest,
@@ -153,6 +154,11 @@ FINDING_STATUS = {
     "FLINK_VERSION_REQUIRED": "INCOMPLETE",
     "FLINK_VERSION_UNVERIFIED": "REVIEW_REQUIRED",
     "FLINK_WRITER_MODEL_UNRESOLVED": "REVIEW_REQUIRED",
+    "FLINK_WRITER_CONTRACT_REQUIRED": "INCOMPLETE",
+    "FLINK_WRITER_CONTRACT_INVALID": "INCOMPLETE",
+    "FLINK_WRITER_OWNERSHIP_UNRESOLVED": "REVIEW_REQUIRED",
+    "FLINK_WRITER_CONTRACT_CONFLICT": "REVIEW_REQUIRED",
+    "FLINK_WRITER_COMBINATION_UNSUPPORTED": "BLOCKED",
     "FLINK_PR2_WRITE_PATH_UNSUPPORTED": "BLOCKED",
     "FLINK_PR3_WRITE_PATH_UNSUPPORTED": "BLOCKED",
     "FLINK_PR4_WRITE_PATH_UNSUPPORTED": "BLOCKED",
@@ -1220,6 +1226,7 @@ def assess_design(
             "table",
             "table_services",
             "write",
+            "writer_contract",
         },
         "contract",
         assessment,
@@ -1303,13 +1310,14 @@ def assess_design(
     writer_model = safety.get("writer_model")
     if writer_model is None:
         assessment.add("FLINK_WRITER_MODEL_UNRESOLVED", "Writer topology is 
required")
-    elif writer_model == "multi_writer":
-        assessment.add(
-            "FLINK_MULTI_WRITER_REVIEW",
-            "The executable contracts support only a confirmed single writer",
-        )
-    elif writer_model != "single_writer":
+    elif not isinstance(writer_model, str) or writer_model not in {
+        "single_writer",
+        "multi_writer",
+    }:
         assessment.add("FLINK_WRITER_MODEL_UNRESOLVED", "Writer topology is 
unresolved")
+    writer_contract_state = assess_writer_contract(contract)
+    for finding in writer_contract_state["findings"]:
+        assessment.add(finding["code"], finding["message"])
     external_catalog = safety.get("external_catalog")
     if type(external_catalog) is not bool:
         assessment.add("FLINK_CATALOG_REQUIREMENT_UNRESOLVED", "Catalog 
requirement is unresolved")
@@ -2055,6 +2063,13 @@ def assess_design(
             "effective_configuration"
         ],
         "catalog_deployment_checks": catalog_state["deployment_checks"],
+        "writer_contract_result": 
writer_contract_state["writer_contract_result"],
+        "multi_writer_capability": 
writer_contract_state["multi_writer_capability"],
+        "normalized_writer_contract": writer_contract_state[
+            "normalized_writer_contract"
+        ],
+        "writer_capability_notes": 
writer_contract_state["writer_capability_notes"],
+        "writer_deployment_checks": 
writer_contract_state["writer_deployment_checks"],
         "finding_codes": [finding["code"] for finding in assessment.findings],
         "findings": assessment.findings,
         "advisory_codes": assessment.advisories,
diff --git 
a/hudi-ai-operator/skills/hudi-architect/validate_flink_existing_table.py 
b/hudi-ai-operator/skills/hudi-architect/validate_flink_existing_table.py
index 5be76ab0eb67..216bc66fd3f9 100644
--- a/hudi-ai-operator/skills/hudi-architect/validate_flink_existing_table.py
+++ b/hudi-ai-operator/skills/hudi-architect/validate_flink_existing_table.py
@@ -34,6 +34,11 @@ from datetime import datetime
 from pathlib import Path
 from typing import Any, TypeGuard
 
+from flink_writer_contract import (
+    CONCURRENCY_MODES,
+    LOCK_CONFIGURATION_CONSISTENCY,
+    assess_writer_contract,
+)
 from validate_flink_capabilities import (
     DEFAULT_MANIFEST,
     load_manifest,
@@ -90,14 +95,19 @@ ALLOWED_EVIDENCE_FACTS = {
         "index_global",
         "index_state_ttl_days",
         "index_type",
+        "lock_configuration_consistency",
+        "lock_provider_class",
         "operation",
         "timestamp_key_generator",
+        "write_concurrency_mode",
     },
     "timeline_summary": {"pending_instants"},
     "writer_inventory": {
+        "active_writers",
         "concurrent_writers",
         "previous_writer_engine",
         "previous_writer_stopped",
+        "schema_evolution_owner_id",
     },
 }
 REQUIRED_EVIDENCE_KINDS = {
@@ -120,6 +130,7 @@ REQUIRED_TABLE_FACTS = {
     "timeline_layout_version",
 }
 LIST_FACTS = {
+    "active_writers",
     "ordering_fields",
     "partition_fields",
     "pending_instants",
@@ -134,6 +145,8 @@ UPPERCASE_FACTS = {
     "previous_writer_engine",
     "record_merge_mode",
     "table_type",
+    "write_concurrency_mode",
+    "lock_configuration_consistency",
 }
 KNOWN_KEY_GENERATORS = {
     "AUTO_RECORD",
@@ -268,6 +281,8 @@ def _normalize_fact(
         if name == "partition_fields":
             # HoodieTableConfig stores custom partition-key types after a 
colon.
             return [item.split(":", 1)[0] for item in values]
+        if name == "active_writers":
+            return sorted(values)
         return values
     if name == "columns":
         return _normalize_columns(value, field, assessment)
@@ -325,7 +340,19 @@ def _normalize_fact(
             f"{field} must be a credential-free path",
         )
         return None
-    return normalized.upper() if name in UPPERCASE_FACTS else normalized
+    normalized = normalized.upper() if name in UPPERCASE_FACTS else normalized
+    enum_values = {
+        "write_concurrency_mode": CONCURRENCY_MODES,
+        "lock_configuration_consistency": LOCK_CONFIGURATION_CONSISTENCY,
+    }
+    allowed_enum_values = enum_values.get(name)
+    if allowed_enum_values is not None and normalized not in 
allowed_enum_values:
+        assessment.add(
+            "FLINK_EXISTING_EVIDENCE_INVALID",
+            f"{field} must be one of {sorted(allowed_enum_values)}",
+        )
+        return None
+    return normalized
 
 
 def _parse_evidence(
@@ -342,6 +369,7 @@ def _parse_evidence(
     evidence_ids: set[str] = set()
     collected: dict[tuple[str, str], list[tuple[Any, str]]] = {}
     in_scope_ids: list[str] = []
+    conflicting_facts: set[str] = set()
     for index, raw_item in enumerate(raw_evidence):
         field = f"evidence[{index}]"
         if not isinstance(raw_item, dict):
@@ -449,12 +477,16 @@ def _parse_evidence(
                 "FLINK_EXISTING_EVIDENCE_CONFLICT",
                 f"Evidence sources {', '.join(source_ids)} disagree about 
{kind}.{name}",
             )
+            conflicting_facts.add(f"{kind}.{name}")
             continue
         observations.setdefault(kind, {})[name] = Observation(first_value, 
source_ids)
     if target_table_path is not None and in_scope_ids:
         observations.setdefault("evidence_scope", {})["table_path"] = 
Observation(
             target_table_path, tuple(in_scope_ids)
         )
+    observations["evidence_conflicts"] = {
+        name: Observation(True, ()) for name in sorted(conflicting_facts)
+    }
     return observations, summaries
 
 
@@ -763,6 +795,21 @@ def assess_reconciliation(
     observations, evidence_summary = _parse_evidence(
         envelope.get("evidence"), target_table_path, assessment
     )
+    writer_evidence: dict[str, Any] = {}
+    writer_evidence_source_ids: dict[str, set[str]] = {}
+    for kind in ("writer_inventory", "flink_writer_config"):
+        for name, observation in observations.get(kind, {}).items():
+            field = f"{kind}.{name}"
+            writer_evidence[field] = observation.value
+            writer_evidence_source_ids[field] = set(observation.source_ids)
+    writer_contract_state = assess_writer_contract(
+        desired,
+        evidence=writer_evidence,
+        conflicting_evidence_fields=set(observations.get("evidence_conflicts", 
{})),
+        evidence_source_ids=writer_evidence_source_ids,
+    )
+    for finding in writer_contract_state["findings"]:
+        assessment.add(finding["code"], finding["message"])
     for kind in sorted(REQUIRED_EVIDENCE_KINDS - set(observations)):
         assessment.add(
             "FLINK_EXISTING_EVIDENCE_REQUIRED", f"{kind} evidence is required"
@@ -951,15 +998,176 @@ def assess_reconciliation(
     concurrent = inventory.get("concurrent_writers")
     stopped = inventory.get("previous_writer_stopped")
     previous_engine = inventory.get("previous_writer_engine")
-    _add_comparison(
-        comparisons,
-        "writer_inventory.concurrent_writers",
-        False,
-        concurrent,
-        mismatch_code="FLINK_MULTI_WRITER_REVIEW",
-        mismatch_result="UNVERIFIED",
-        assessment=assessment,
+    raw_safety = desired.get("safety")
+    safety = raw_safety if isinstance(raw_safety, dict) else {}
+    writer_model = safety.get("writer_model")
+    if writer_model == "single_writer":
+        _add_comparison(
+            comparisons,
+            "writer_inventory.concurrent_writers",
+            False,
+            concurrent,
+            mismatch_code="FLINK_MULTI_WRITER_REVIEW",
+            mismatch_result="UNVERIFIED",
+            assessment=assessment,
+        )
+    elif concurrent is None:
+        comparisons.append(
+            {
+                "desired": None,
+                "field": "writer_inventory.concurrent_writers",
+                "observed": None,
+                "result": "UNVERIFIED",
+                "source_ids": [],
+            }
+        )
+        assessment.add(
+            "FLINK_EXISTING_EVIDENCE_REQUIRED",
+            "Evidence is required for writer_inventory.concurrent_writers",
+        )
+    else:
+        comparisons.append(
+            {
+                "desired": None,
+                "field": "writer_inventory.concurrent_writers",
+                "observed": concurrent.value,
+                "result": "UNVERIFIED",
+                "source_ids": list(concurrent.source_ids),
+            }
+        )
+    active_writers = inventory.get("active_writers")
+    if writer_model == "single_writer" and active_writers is not None:
+        comparisons.append(
+            {
+                "desired": {"maximum_count": 1},
+                "field": "writer_inventory.active_writers",
+                "observed": active_writers.value,
+                "result": (
+                    "MATCH" if len(active_writers.value) <= 1 else "UNVERIFIED"
+                ),
+                "source_ids": list(active_writers.source_ids),
+            }
+        )
+    normalized_writer_contract = 
writer_contract_state["normalized_writer_contract"]
+    if normalized_writer_contract:
+        declared_writer_ids = sorted(
+            [normalized_writer_contract["primary_writer_id"]]
+            + [
+                writer["writer_id"]
+                for writer in normalized_writer_contract["additional_writers"]
+            ]
+        )
+        if active_writers is not None:
+            active_writer_result = (
+                "MATCH"
+                if set(active_writers.value) <= set(declared_writer_ids)
+                else "UNVERIFIED"
+            )
+            comparisons.append(
+                {
+                    "desired": {"allowed_writer_ids": declared_writer_ids},
+                    "field": "writer_inventory.active_writers",
+                    "observed": active_writers.value,
+                    "result": active_writer_result,
+                    "source_ids": list(active_writers.source_ids),
+                }
+            )
+            if active_writer_result != "MATCH":
+                assessment.add(
+                    "FLINK_WRITER_CONTRACT_CONFLICT",
+                    "Existing-table value differs at 
writer_inventory.active_writers",
+                )
+        schema_owner = inventory.get("schema_evolution_owner_id")
+        if schema_owner is not None:
+            _add_comparison(
+                comparisons,
+                "writer_inventory.schema_evolution_owner_id",
+                
normalized_writer_contract["ownership"]["schema_evolution_owner_id"],
+                schema_owner,
+                mismatch_code="FLINK_WRITER_CONTRACT_CONFLICT",
+                mismatch_result="UNVERIFIED",
+                assessment=assessment,
+            )
+    elif writer_model != "single_writer" and active_writers is not None:
+        comparisons.append(
+            {
+                "desired": None,
+                "field": "writer_inventory.active_writers",
+                "observed": active_writers.value,
+                "result": "UNVERIFIED",
+                "source_ids": list(active_writers.source_ids),
+            }
+        )
+
+    observed_concurrency = (
+        normalized_writer_contract.get("observed_concurrency")
+        if normalized_writer_contract
+        else None
     )
+    config_observations = observations.get("flink_writer_config", {})
+    concurrency_expectations = {
+        "writer_config.write_concurrency_mode": (
+            observed_concurrency.get("mode")
+            if isinstance(observed_concurrency, dict)
+            else None,
+            config_observations.get("write_concurrency_mode"),
+        ),
+        "writer_config.lock_provider_class": (
+            observed_concurrency.get("lock_provider_class")
+            if isinstance(observed_concurrency, dict)
+            else None,
+            config_observations.get("lock_provider_class"),
+        ),
+        "writer_config.lock_configuration_consistency": (
+            observed_concurrency.get("lock_configuration_consistency")
+            if isinstance(observed_concurrency, dict)
+            else None,
+            config_observations.get("lock_configuration_consistency"),
+        ),
+    }
+    for field, (expected, observed) in concurrency_expectations.items():
+        if observed is None:
+            continue
+        if writer_model == "multi_writer" and expected is not None:
+            _add_comparison(
+                comparisons,
+                field,
+                expected,
+                observed,
+                mismatch_code="FLINK_WRITER_CONTRACT_CONFLICT",
+                mismatch_result="UNVERIFIED",
+                assessment=assessment,
+            )
+        elif writer_model == "single_writer" and field.endswith(
+            "write_concurrency_mode"
+        ):
+            _add_comparison(
+                comparisons,
+                field,
+                "SINGLE_WRITER",
+                observed,
+                mismatch_code="FLINK_MULTI_WRITER_REVIEW",
+                mismatch_result="UNVERIFIED",
+                assessment=assessment,
+            )
+        else:
+            result = "UNVERIFIED"
+            if writer_model == "single_writer":
+                result = (
+                    "UNVERIFIED"
+                    if field.endswith("lock_configuration_consistency")
+                    and observed.value == "CONFLICTING"
+                    else "NOT_APPLICABLE"
+                )
+            comparisons.append(
+                {
+                    "desired": None,
+                    "field": field,
+                    "observed": observed.value,
+                    "result": result,
+                    "source_ids": list(observed.source_ids),
+                }
+            )
     if transition in {"same_job_restore", "cold_start_new_job", 
"flink_writer_takeover"}:
         _add_comparison(
             comparisons,
@@ -1073,10 +1281,14 @@ def assess_reconciliation(
         change_disposition = "DEPLOYMENT_CONFIGURATION_CHANGE"
     else:
         change_disposition = "UNVERIFIED"
-    deployment_checks = [
-        {"check": "confirm_single_writer", "required": True},
-        {"check": "recheck_pending_instants", "required": True},
-    ]
+    deployment_checks = []
+    if writer_model == "single_writer":
+        deployment_checks.append(
+            {"check": "confirm_single_writer", "required": True}
+        )
+    deployment_checks.append(
+        {"check": "recheck_pending_instants", "required": True}
+    )
     if transition == "cold_start_new_job" and desired.get("contract_schema") 
in (2, 3):
         deployment_checks.append(
             {"check": "verify_index_bootstrap_before_first_checkpoint", 
"required": True}
@@ -1102,6 +1314,13 @@ def assess_reconciliation(
         "catalog_deployment_checks": desired_result.get(
             "catalog_deployment_checks", []
         ),
+        "writer_contract_result": 
writer_contract_state["writer_contract_result"],
+        "multi_writer_capability": 
writer_contract_state["multi_writer_capability"],
+        "normalized_writer_contract": writer_contract_state[
+            "normalized_writer_contract"
+        ],
+        "writer_capability_notes": 
writer_contract_state["writer_capability_notes"],
+        "writer_deployment_checks": 
writer_contract_state["writer_deployment_checks"],
         "finding_codes": finding_codes,
         "findings": assessment.findings,
         "advisory_codes": assessment.advisories,

Reply via email to