[ 
https://issues.apache.org/jira/browse/NIP-36?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Bob Paulin updated NIP-36:
--------------------------
    Attachment: image-2026-10-16-07-49-01-251.png

> Lockable Connector Properties
> -----------------------------
>
>                 Key: NIP-36
>                 URL: https://issues.apache.org/jira/browse/NIP-36
>             Project: NiFi Improvement Proposal
>          Issue Type: Improvement
>            Reporter: Bob Paulin
>            Priority: Major
>         Attachments: image-2026-10-16-07-49-01-251.png
>
>
> h2. Motivation
> Some Connector configuration properties describe decisions that cannot be 
> safely changed after the Connector has started producing data. Typical 
> examples:
>  * The output format written to a destination. Once objects have been written 
> to an S3 bucket as Parquet, silently switching the property to Avro produces 
> a bucket whose contents can no longer be read by a single reader.
>  * The physical destination identifier. The S3 bucket, Kafka topic, or 
> object-store prefix that the Connector writes into. Changing this after data 
> has been written silently forks the destination and leaves the previously 
> written data orphaned.
>  * The identity used to track source progress. The Kafka consumer group id or 
> source offset-tracking key. Changing this after commit either re-reads 
> already-processed data or leaves committed offsets behind, in both cases 
> producing duplicates or gaps.
> Today there is no first-class way for a Connector author to declare that such 
> a property is frozen after the Connector has started. The intent lives only 
> in ad hoc validation code inside each Connector, is not exposed to the REST 
> API, is not visible in the extension manifest, and is not visible in the UI. 
> Users can freely edit these fields in the configuration dialog and see a 
> generic error only when they attempt to apply the change, if the Connector 
> happens to defend it at all.
> This proposal makes _"once the Connector has been started at least once, this 
> property is frozen at its current value"_ a first-class descriptor attribute, 
> enforced by the framework, surfaced through the REST API and extension 
> manifest, and rendered as read-only in the UI so the user never has the 
> opportunity to make an unappliable change in the first place.
> h2. Scope
> Out of Scope:
> Immutable Properties for Processors or Controller Services are not part of 
> the scope of this NIP.    Processors or Controller Services have done without 
> this feature and have implemented ways to handle property value changes 
> within the component code.  Connectors integrate an entire flow worth of 
> components together so trying to force connectors to handle any possible 
> connector property change may not always be possible.  
> {{nifi-api}} additions:
>  * {{ConnectorPropertyDescriptor.isImmutable()}} and 
> {{{}ConnectorPropertyDescriptor.Builder.immutable(boolean){}}}, defaulting to 
> {{{}false{}}}. {{Builder.from(other)}} copies the flag.
>  * {{VersionedConnector.hasBeenStarted}} ({{{}boolean{}}}) with 
> getter/setter, serialized alongside the Connector's other flow-level metadata.
>  * {{XmlConnectorDocumentationWriter.writeConnectorProperty(...)}} emits a 
> new {{<immutable>}} element so the extension manifest exposes the flag.
> Framework additions:
>  * New methods on {{{}org.apache.nifi.components.connector.ConnectorNode{}}}: 
> {{boolean hasBeenStarted()}} and {{{}void setHasBeenStarted(boolean){}}}.
>  * {{StandardConnectorNode}} sets {{hasBeenStarted = true}} after every 
> successful transition to {{{}RUNNING{}}}, and rejects changes to any 
> immutable property in {{setConfiguration(...)}} and 
> {{replaceWorkingConfiguration(...)}} once the flag is set. Comparison is done 
> on the raw {{ConnectorValueReference}} instances so asset and secret 
> references are compared by identity, not by their resolved values.
>  * {{VersionedComponentFlowMapper.mapConnector(...)}} persists the flag; 
> {{StandardConnectorRepository.createConnectorNode(VersionedConnector)}} 
> restores it via {{{}ConnectorNode.setHasBeenStarted(...){}}}. The flag 
> survives restarts, flow imports, and Connector-version upgrades.
> Model / DTO / manifest plumbing:
>  * {{ConnectorPropertyDescriptorDTO}} (nifi-client-dto) gains an 
> {{immutable}} field, populated by 
> {{{}DtoFactory.createConnectorPropertyDescriptorDto(...){}}}.
>  * {{ConnectorDTO}} (nifi-client-dto) gains a {{hasBeenStarted}} field, 
> populated by {{DtoFactory}} from {{{}ConnectorNode.hasBeenStarted(){}}}, so 
> that any client can determine whether a Connector's immutable properties are 
> currently locked.
>  * {{org.apache.nifi.extension.manifest.ConnectorProperty}} and 
> {{org.apache.nifi.c2.protocol.component.api.ConnectorPropertyDescriptor}} 
> gain the same {{immutable}} field; 
> {{StandardRuntimeManifestBuilder.getConnectorPropertyDescriptor(...)}} copies 
> it through.
> UI additions ({{{}nifi-frontend{}}}):
>  * The Connector property-editor components consume {{descriptor.immutable}} 
> together with {{{}connector.hasBeenStarted{}}}. When both are true, the 
> corresponding form control is rendered as disabled and no editable value 
> control is offered.
>  * A locked visual indicator (padlock icon plus tooltip text) is displayed 
> next to the disabled control, explaining that the property was fixed at the 
> value from the Connector's first successful start and cannot be changed..
> h2. Description
> Descriptor contract. 
> A property built with {{.immutable(true)}} behaves identically to a 
> non-immutable property until the Connector transitions to {{RUNNING}} for the 
> first time. Attempts to change it before that point succeed, which preserves 
> normal initial configuration, verification, and staged flow deployment. 
> Immutability begins the moment {{StandardConnectorNode}} sets 
> {{{}hasBeenStarted = true{}}}, and from then on the value locked in is 
> whatever was in the active configuration at the point of transition.
> Enforcement point. 
> The check lives in a single private helper — 
> {{StandardConnectorNode.verifyImmutablePropertiesUnchanged(stepName, 
> incoming, replace)}} — called from the two mutation entry points a user 
> request can reach:
>  * {{setConfiguration(String, StepConfiguration)}} — the merge path used by 
> REST configuration updates and by {{{}ConnectorTestRunner.configure(...){}}}.
>  * {{replaceWorkingConfiguration(String, StepConfiguration)}} — the 
> whole-step replacement path used by configuration providers.
> Restore paths (flow deserialization, snapshot inheritance, 
> {{{}recreateWorkingFlowContext{}}}) deliberately bypass the check because 
> they must be able to reconstruct the Connector's existing state without the 
> framework second-guessing itself. A violating change throws 
> {{FlowUpdateException}} with a message that names the offending property, the 
> step, and the Connector.
> Persistence. 
> {{hasBeenStarted}} is stored on {{{}VersionedConnector{}}}, not on the 
> descriptor. On save, {{VersionedComponentFlowMapper}} reads 
> {{{}connectorNode.hasBeenStarted(){}}}. On load, 
> {{StandardConnectorRepository}} reads the field off the 
> {{VersionedConnector}} and calls {{setHasBeenStarted(true)}} before the 
> Connector is exposed to any user request, so enforcement is active 
> immediately after restart. Because the flag lives on the Connector and not on 
> the descriptor, values locked under Connector v1 remain locked under 
> Connector v2 even if v2 changes the descriptor set: whether a property is 
> immutable is decided by the current v2 descriptors, and _"the Connector has 
> been started"_ is decided by the persisted flag.
> Backwards compatibility.
>  {{isImmutable()}} defaults to {{{}false{}}}, so existing Connectors are 
> unaffected. {{VersionedConnector.hasBeenStarted}} defaults to {{false}} in 
> the JSON representation, so flows saved by older frameworks come back 
> unlocked, which is the same behaviour as today. The new DTO / manifest / c2 
> fields are additive, and building an older Connector against the new 
> {{nifi-api}} continues to compile because both new descriptor methods have 
> default implementations.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to