fix: three decision-flow integration defects from the live acceptance run - #1096
Merged
Merged
Conversation
…ded lifecycle edges Defect 1: a decision raised through the delegation event (dossiq's requestDecision node) arrived without the schema-required text: the listener dropped the delegation context, and createDecision() wrote title/text as empty strings and decisionDate as an invalid empty date-time. The object saved (the create path skipped required-property validation) but every later PUT was then rejected on exactly that validation, leaving the decision un-updatable (observed live: decision 7f2dc8f4). createDecision() now derives text from the supplied text, else the delegation context (question/reasoning/motivation/description), else a translated fallback naming the source app and subject; title falls back to the subject label, then the source app; an empty decisionDate is omitted. The listener forwards payload.context. Defect 2: DecisionTransitionGuard permits deliberating->decided (decide-without-vote, licensed by the decision-management spec for operational domains) and archive-from-decided, but the schema's x-openregister-lifecycle declared neither edge, so OR's LifecycleValidationListener rejected both at save time: dead auth code. Both edges are now declared (canonical + mock register), with the domain-policy guard still filtering on top, and DecisionTransitionMatrixTest pins guard<->schema parity in both directions so the accepted grammar stays the executable one.
…hema no register carries
Every decidiq audit append failed with 'Schema slug audit-trail is not
carried by register decidiq': the C3 board-portal retirement mapped
board-audit-log-entry to OR's auditTrail but only renamed the schema slug
the writer saves to, so every governance action since (decision
transitions included) silently produced no audit row while the action
itself persisted.
AuditLogService now consumes OR's audit surface (ADR-022, the
audit-trail-fleet-wide-consumption spec, and the same
AuditTrailMapper::createAuditTrailEntry() path dossiq's parafering audit
uses): appends land as namespaced decidiq.audit.{action} entries attached
to the acted-on object, with actor, the full uid list and the payload in
the entry context; hash chaining and sealing are the platform's
(AuditSealJob / AuditHashService), so verify() delegates to
verifyChain() and query()/export() read the decidiq namespace back out
of the trail. Failure is loud by contract: an unavailable surface or an
unresolvable uid list logs at error level and returns success=false,
pinned by tests.
Also fixes three appends that were ALWAYS refused with 'Unknown action'
because their action names were never in ACTIONS: integration-create,
integration-subscribe, and the retention purge (which sent the dotted
transcript.retention.purge; the job now sends
transcript-retention-purge, and its test asserts membership in ACTIONS
so the fake can no longer accept what production refuses).
New OR test stubs (AuditTrail, AuditTrailMapper, AuditHashService)
follow the signature-parity contract (#399).
…analyzers PHPMD flagged DecisionIntegrationService at complexity 63 (threshold 55) after the title/text derivation landed, so the derivation moves to its own small collaborator, DelegatedDecisionDefaults, which the service consumes. AuditLogService imports DateTimeZone properly (MissingImport), psalm.xml gains referencedClass entries for the three OR audit classes (cross-app, loaded at runtime), and one new test's @SPEC anchor is repointed at the decidesk-decision-events spec that actually exists (gate-46).
rubenvdlinde
requested review from
Rem-Dam,
SudoThijn,
WilcoLouwerse,
bbrands02,
remko48 and
rjzondervan
as code owners
September 1, 2026 21:19
Contributor
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ❌ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-01 21:31 UTC
Download the full PDF report from the workflow artifacts.
The nl browser catalogue was rebuilt by hand with lexically sorted keys, but scripts/build-l10n-js.js emits the json's own key order (the numeric 2026/2027 keys lead), so check:l10n-js called it stale. Regenerated via npm run l10n:build; check:l10n-js reports all 26 catalogues up to date.
Contributor
Quality Report — ConductionNL/decidiq @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-manifest | ✅ | ||||
| check-nav-ceiling | ✅ | ||||
| test-l10n | ✅ | ||||
| format | ✅ | ||||
| check-l10n-js | ✅ | ||||
| check-schema-l10n | ✅ | ||||
| composer | ✅ | ✅ 104/104 | |||
| npm | ✅ | ✅ 537/537 | |||
| app:check-code | ⏭️ | ||||
| info.xml | ✅ | ||||
| REUSE | ❌ | ||||
| PHPUnit | ✅ | ||||
| Newman | ✅ | ||||
| Playwright | ⏭️ deferred — runs on the promotion into beta/main, not on a pull request into development | ||||
| Hydra gates | ✅ |
Quality workflow — 2026-09-01 22:00 UTC
Download the full PDF report from the workflow artifacts.
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.
What this fixes
Three defects found by the 2026-09-01 live browser acceptance run of the dossiq to decidiq decision delegation flow.
1. Flow-created decisions were schema-invalid from birth
A decision raised through the delegation event (dossiq's requestDecision node) arrived without the schema-required
text: the listener dropped the delegation context, andcreateDecision()wrotetitle/textas empty strings anddecisionDateas an empty date-time. The object saved anyway, and every later PUT was then rejected on exactly the required-property validation the create skipped, leaving the decision un-updatable (observed live: decision 7f2dc8f4).createDecision()now derivestextfrom the supplied text, else the delegation context (question, reasoning, motivation, description), else a translated fallback naming the source app and subject.titlefalls back to the subject label, then the source app. An emptydecisionDateis omitted. The listener forwardspayload.context. The derivation lives in the newDelegatedDecisionDefaultscollaborator; strings ship in en and nl.Note for a separate openregister issue: the create path accepted an object that violates the schema's own
requiredlist, while PUT enforces it. That half belongs to openregister and is reported, not fixed here.2. The decide-without-vote edge was dead auth code
DecisionTransitionGuardpermits deliberating to decided (allowDecideWithoutVote, licensed by the decision-management spec for operational domains), but the schema'sx-openregister-lifecyclenever declared that edge, so OR rejected it at save time: "No transition allows moving lifecycle from deliberating to decided". The same held for archiving fromdecided, which a rejected decision needs because it can never enact.Both edges are now declared in the canonical and mock registers, with the domain-policy guard still filtering on top.
DecisionTransitionMatrixTestwalks the full guard matrix against both registers in both directions, so the accepted grammar stays the executable one.3. Every transition silently lost its audit append
AuditLogServicesaved to schema slugaudit-trail, which no register carries since the C3 board-portal retirement mapped it to "OR auditTrail" but never repointed the writer. Every governance audit append since then failed silently.The service now consumes OR's audit surface per ADR-022 and the audit-trail-fleet-wide-consumption spec, the same
AuditTrailMapper::createAuditTrailEntry()path dossiq's parafering audit uses. Appends land asdecidiq.audit.{action}entries attached to the acted-on object; hash chaining and sealing are the platform's, soverify()delegates toverifyChain()and query/export read the namespace back out of the trail. Failure is loud by contract: error log plussuccess=false, pinned by tests. Three appends that were always refused with "Unknown action" (integration-create, integration-subscribe, the retention purge) are now in the vocabulary.Checks
developmentbecause hydra-gates v1.10.0 vendors a manifest schema that predates theflowpage type. The manifest validates against the corrected canonical schema already on ConductionNL/.github main (PR [OpenSpec] 2026-05-11-p2-motion-and-voting-other-t1 #602 and [OpenSpec] 2026-06-14-decision-methods #658); the next hydra-gates tag clears it. This branch changes no manifest input.Try it: raise a decision from a dossiq flow, decide it without a vote in an operations body, and read the audit row back with
GET /api/audit-trails?objectUuid={decisionId}.