intent: an amended source rewrites the posting it already made (#7071) - #7086
Merged
Conversation
The amend path is ordinary and documented - issue, reject, add a line, issue again - and it raises a `postings:` moment a SECOND time. The generated handler answered that with its idempotency test (an existing post whose item count reached the derived one) and did nothing, so the journal entry kept the amounts of the first issue while the invoice it references said something else: no second entry (right) and a ledger short by the difference (wrong), with nothing anywhere saying so. Idempotence was answering "have I handled this source?" when the question is "does the post still say what the source says?". The handler now derives the WHOLE content first - the header assignments and every guarded item row - and compares it with the post the back-reference finds: identical is a redelivery (no-op), different is either a half-post to complete or an amendment to rewrite the existing post from. One source still has at most one post. The comparison is order-insensitive (row order is not a query guarantee) over the union of every cell the rows assign, and numbers compare by value so a rescaled amount is not a change. The rewrite stops where the created document's own lifecycle says somebody has taken it over, which the posting itself established: it is rewritable while its `function: EntityStatus` relation still holds the `init:` its own create wrote (no status lifecycle at all - nothing to act on - is always rewritable). Past that the divergence is logged naming both documents and left to a correcting entry (`reverses:`) - overwriting a document an accountant has acted on is worse than the divergence it repairs. `amendableGuard` and `itemComparedProps` are pre-rendered into the glue like everything else, and both fall back to the previous behaviour for a `.glue` written before them. Fixes #7071
This was referenced Sep 6, 2026
This was referenced Sep 8, 2026
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The defect
The amend path is ordinary and documented — issue, reject, add a line, issue again — and it raises a
postings:moment a second time. The generated handler answered that with its idempotency test (an existing post whose item count reached the derived one) and did nothing:No second entry (right) and a ledger 60.00 short (wrong), with nothing in the application saying so. Idempotence was answering "have I handled this source?" when the question is "does the post still say what the source says?".
The fix
The handler derives the WHOLE content first — the header assignments and every guarded item row — and compares it with the post the back-reference finds:
update, items replaced. One source still has at most one post, and the half-post resume (an item write that failed after the document was saved) is now the same case rather than a separate rule.The comparison is order-insensitive (row order is not a query guarantee) over the union of every cell the item rows assign, and numbers compare by value so a rescaled amount is not read as a change.
The rewrite stops at the created document's own lifecycle, which the posting itself established: it is rewritable while its
function: EntityStatusrelation still holds theinit:value its own create wrote (a target with no status lifecycle has nothing to act on, so it is always rewritable). Past that — the entry is posted, approved, closed — the divergence is logged naming both documents and the entry is left alone. Unwinding a document somebody has acted on is a correcting entry's job (reverses:), not a silent overwrite; that boundary is deliberate and stated, not an oversight.No new DSL key.
amendableGuardanditemComparedPropsare pre-rendered into the glue like everything else (the expansions convention), andGlueGenerator.bindPostingdefaults both so a.gluewritten before this change renders exactly what it used to.Verification
GluePostingsAmendTest(new) — the guard for a target with a status +init:, for one without, and the compared-property union.IntentEngineIT.postings_generates_the_idempotent_resumable_handler— updated to the new emitted shape;a_post_is_not_rewritten_once_the_created_document_has_left_the_status_it_was_created_in(new) asserts the guard and that the refusal is reported.IntentEmissionCoverageIT— green, and it publishes and compiles the generated handlers, so both the plain and thereverses:shape are proven to compile; two assertions added for the comparison and the in-place rewrite.mvn formatter:validateand thereleasejavadoc profile on the touched modules.Docs: dirigible.io
help/intent/dsl-reference.md, the in-repoengine-intent/CLAUDE.md+intent-assistant-guide.md, and an IntentFile spec proposal + site page.Fixes #7071