Skip to content

fix: three decision-flow integration defects from the live acceptance run - #1096

Merged
rubenvdlinde merged 4 commits into
developmentfrom
fix/decision-flow-integration-defects
Sep 1, 2026
Merged

fix: three decision-flow integration defects from the live acceptance run#1096
rubenvdlinde merged 4 commits into
developmentfrom
fix/decision-flow-integration-defects

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

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, and createDecision() wrote title/text as empty strings and decisionDate as 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 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. The derivation lives in the new DelegatedDecisionDefaults collaborator; strings ship in en and nl.

Note for a separate openregister issue: the create path accepted an object that violates the schema's own required list, 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

DecisionTransitionGuard permits deliberating to decided (allowDecideWithoutVote, licensed by the decision-management spec for operational domains), but the schema's x-openregister-lifecycle never declared that edge, so OR rejected it at save time: "No transition allows moving lifecycle from deliberating to decided". The same held for archiving from decided, 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. DecisionTransitionMatrixTest walks 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

AuditLogService saved to schema slug audit-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 as decidiq.audit.{action} entries attached to the acted-on object; hash chaining and sealing are the platform's, so verify() delegates to verifyChain() and query/export read the namespace back out of the trail. Failure is loud by contract: error log plus success=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

  • php lint, phpcs, PHPMD (per subdirectory, both rulesets), Psalm, PHPStan: clean
  • PHPUnit: 1305 unit tests green (22 pre-existing environment skips)
  • hydra gates, diff-scoped: 34 of 35 applicable gates pass; gate-53 fails identically on development because hydra-gates v1.10.0 vendors a manifest schema that predates the flow page 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}.

…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).
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/decidiq @ 842be8b

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.
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/decidiq @ a40dd26

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.

@rubenvdlinde
rubenvdlinde merged commit 86fa9d3 into development Sep 1, 2026
49 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant