Skip to content

feat(automation): add generateDocument automation action - #12

Merged
rubenvdlinde merged 5 commits into
developmentfrom
wip/automation-document-action
Jul 24, 2026
Merged

feat(automation): add generateDocument automation action#12
rubenvdlinde merged 5 commits into
developmentfrom
wip/automation-document-action

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

Summary

Implements OpenSpec change automation-document-action: a new generateDocument automation action kind lets an automation trigger Docudesk document generation on object create/update/delete or a lifecycle transition — no interactive browser click required.

  • AutomationCompilerService: generateDocument added to the v1 matrix on object-created|object-updated|object-deleted|lifecycle-transition; fail-closed on schedule/manual. Compile-time validation only (templateId present, output a known non-empty set, notify never alone) — no compile-time Docudesk-side artifact, since correspondence/generate is stateless. Throws UnsupportedAutomationCombinationException naming the missing docudesk dependency when Docudesk is absent.
  • DocumentGenerationService (new): the imperative half. attach writes the returned bytes to Nextcloud Files (OCP\Files\IRootFolder) and sets { "ref": "<fileId>" } on the object's generatedDocument field. download-link writes to OpenBuild's own app-private storage (OCP\Files\IAppData — never the user's Files tree) behind a random ~24h token, served by the new GeneratedDocumentController. notify reuses the existing RuleActionDispatcher send-notification path (no second notification implementation).
  • DocumentGenerationListener (new): trigger-fire dispatch, mirrors AutomationApprovalTriggerListener's shape for the sibling approval action kind.
  • useDocudeskTemplates.js (new, shared): the ONE Docudesk template-list fetch, now used by both DocumentTemplateAttachmentDialog and the new AutomationEditDialog picker.
  • AutomationEditDialog.vue: generateDocument action editor — template picker + output-mode multi-select — disabled with a missing-app hint via useAppStatus('docudesk'), and matrix-aligned save validation mirroring the compiler's own rules.

How REQ-DDT-006 was honored

docudesk-document-templates REQ-DDT-006 hard-forbids importing Docudesk PHP classes and requires "the caller's Nextcloud session." An automation trigger has no interactive session, so DocumentGenerationService impersonates the Application owner via the existing JobOwnerImpersonator (lib/Service/JobOwnerImpersonator.php — no new impersonation mechanism) and makes one internal HTTP call to the same pinned POST /apps/docudesk/api/correspondence/generate route — never a class import, never a new route.

Documented extension of design.md's transport detail: design.md's literal wording (IUserSession::setUser() "makes that user the active session for the duration of one internal HTTP call") describes the authorization intent correctly but not, by itself, a working HTTP transport — setUser() only changes the current PHP process's session, and IClientService's call is a genuine network request Nextcloud's auth middleware evaluates independently on arrival. DocumentGenerationService realizes the same "one call, as the impersonated owner" intent by minting a short-lived Nextcloud login token for the impersonated user (via NC core's OC\Authentication\Token\IProvider — the same mechanism behind NC's own "app password" feature, resolved defensively/lazily exactly like JobOwnerImpersonator resolves its own optional collaborators) and presenting it as HTTP Basic Auth on that one call, explicitly invalidating the token again immediately after use. IToken::TEMPORARY_TOKEN is used rather than ONETIME_TOKEN because the latter isn't present on every nextcloud/ocp stub version this app's composer.json range resolves to — the explicit invalidate-after-use call gives the same effective one-shot lifetime. Both openspec/specs/docudesk-document-templates/spec.md REQ-DDT-006 and the archived change's own spec name this transport explicitly.

REQ-DDT-006's pinned-route list itself does not grow — correspondence/generate gets a second, still-single-route caller shape, asserted by a new Newman item that pins the automation-shaped request body against the same route item 4 already covers.

Test results (exact commands)

docker run --rm -v $PWD:/app -w /app nextcloud:34.0.0-apache php vendor/bin/phpunit -c phpunit-unit.xml
→ OK, but there were issues! Tests: 699, Assertions: 2122, PHPUnit Warnings: 1 (no coverage driver, harmless).

docker run --rm -v $PWD:/app -w /app nextcloud:34.0.0-apache php vendor/bin/phpstan analyse --no-progress
→ [OK] No errors

docker run --rm -v $PWD:/app -w /app nextcloud:34.0.0-apache php vendor/bin/psalm --no-progress
→ No errors found!

docker run --rm -v $PWD:/app -w /app nextcloud:34.0.0-apache php vendor/bin/phpcs --standard=phpcs.xml <touched lib/ files>
→ clean (0 errors)

npx eslint <touched src/ files>
→ 0 errors, 51 pre-existing-style warnings (jsdoc @spec tag not recognized — repo-wide convention)

npx vitest run
→ Test Files 127 passed (127); Tests 1236 passed (1236)

Baseline before this PR: 672 PHPUnit / 1224 vitest — this PR adds 27 PHPUnit + 12 net vitest tests, all green.

Hydra mechanical gates (--scope-to-diff)

35/39 green, including all three gates named as this task's acceptance criterion: spec-coverage (gate-16), no-phantom-cross-app-rpc (gate-27), controller-exception-translation (gate-49).

4 remaining findings are pre-existing/out-of-scope, not introduced by this diff's substance:

  • forbidden-patterns: a pre-existing, already-documented (phpcs:ignore comment in place) error_log() call in lib/AppInfo/Application.php at a line this PR never touches — flagged only because the gate scans the whole file once any part of it is in the diff.
  • effective-manifest-crossref: 2 pre-existing findings against src/manifest.json /deepLinks/0 (an unrelated Example widget) — this PR never touches any manifest file.
  • spec-anchor-existence: this app's fleet-wide @spec .../tasks.md#N.N convention (checkbox-index fragments, not real Markdown headings) predates this change and is used identically by the just-merged sibling automation-approval-steps (PR feat(automation-approval-steps): approval action kind, on-approve/reject follow-ups, My Approvals widget #10) — not something this PR's scope should unilaterally rewrite across the codebase.
  • e2e-coverage: 21 of the original 30 findings were pre-existing automation-designer/automation-approval-steps scenarios pulled into scope only because this PR edits shared requirement bodies in the same canonical spec file; this PR's own 9 new/modified scenarios are all closed (2 real Playwright @e2e markers + 7 reason-bearing @e2e exclude for backend/PHPUnit/Newman-covered contracts).

Vendor hazard

vendor/ was never staged or committed on this branch — confirmed via git status --porcelain before every commit and git log --stat origin/development..HEAD | grep vendor (no output) after. The root-owned vendor/ directory created by in-container composer install was removed via a container rm -rf before finishing.

Test plan

  • composer check:strict-equivalent local checks (phpunit/phpstan/psalm/phpcs) all green
  • npx eslint / npx vitest run green
  • Hydra gates: 3 required gates green, 4 pre-existing/out-of-scope findings documented above
  • openspec validate automation-document-action passes; change archived to openspec/changes/archive/2026-07-24-automation-document-action/
  • Live E2E against a running instance — deferred per project policy (no deploy to the shared dev instance); Playwright test file written and CI-run only

🤖 Generated with Claude Code

Conduction Release Bot added 5 commits July 24, 2026 14:30
Implements the automation-document-action OpenSpec change: a new
generateDocument action kind lets an automation trigger Docudesk document
generation on object create/update/delete/lifecycle-transition, without
ever importing a Docudesk PHP class or bypassing REQ-DDT-006's closed
integration contract.

Backend:
- AutomationCompilerService: generateDocument added to the v1 matrix on
  event/lifecycle-transition triggers; compile-time validation (templateId
  present, output non-empty, notify never alone) and a fail-closed
  UnsupportedAutomationCombinationException when Docudesk is absent.
- DocumentGenerationService: owner-impersonated (JobOwnerImpersonator)
  internal HTTP call to the pinned POST /apps/docudesk/api/correspondence/
  generate route, authenticated via a short-lived NC login token minted
  through OC\Authentication\Token\IProvider and invalidated immediately
  after use. attach writes to Nextcloud Files + a {ref} field on the
  object; download-link writes to OpenBuild's own app-private storage
  (never the user's Files tree) behind a random ~24h token served by the
  new GeneratedDocumentController; notify reuses RuleActionDispatcher's
  existing send-notification path.
- DocumentGenerationListener: trigger-fire dispatch, mirrors
  AutomationApprovalTriggerListener's shape for the sibling approval kind.

Frontend:
- useDocudeskTemplates.js: shared template-list fetch, now used by BOTH
  DocumentTemplateAttachmentDialog and the new automation editor picker
  (no second implementation).
- AutomationEditDialog.vue: generateDocument action editor (template
  picker + output-mode multi-select), missing-app degradation via
  useAppStatus('docudesk'), and matrix-aligned save validation.

Tests: 27 new PHPUnit tests (compiler, service, listener, controller,
source-tree Docudesk-import guard) + 21 new/updated vitest tests. Full
suites green (699 PHPUnit / 1236 vitest), phpcs/phpstan/psalm clean on
touched files.
- Revert the unrelated SetupController.php phpstan tidy-up: it pulled 3
  pre-existing @NoAdminRequired/admin-body semantic-auth findings into this
  diff's gate scope for a file this change has no other reason to touch.
- Add the @SPEC tag templateToOption() was missing (gate-16 spec-coverage).
… pin

- Playwright: compose a generateDocument automation on a lifecycle
  transition (template picker/free-text degrade, output-mode select) and
  the schedule-trigger fail-closed block, mirroring the existing
  approval-action e2e suite's structure. CI-run only, not executed against
  the shared dev instance in this session.
- Newman: item 6 pins the automation-triggered call to the SAME
  correspondence/generate route + request shape item 4 already asserts.
- tasks.md: check off 4.1-7.2 (all 18 tasks complete).
…pecs

- Move openspec/changes/automation-document-action/ to
  openspec/changes/archive/2026-07-24-automation-document-action/.
- Sync canonical specs: automation-document-action (new capability, ADDED
  requirements, Status done), automation-designer (REQ-AUTD-002/003/004
  MODIFIED with the generateDocument scenarios, Status done — last active
  change), docudesk-document-templates (REQ-DDT-006 MODIFIED to name the
  owner-impersonated automation caller shape; harden-xss-dos-csrf still
  active so Status stays in-progress).
- CHANGELOG.md: add the 0.7.5 entry.
- docs/automation-designer.md: extend the compilation matrix with the
  Require approval and Generate document columns (both were missing —
  approval predates this PR; documented together since they share the
  same "needs a concrete fired object" constraint).
Add @e2e Playwright markers for the two scenarios this PR's automations
e2e suite exercises, and reason-bearing @e2e exclude annotations (backend/
service contract, already covered by PHPUnit + Newman) for the remaining
scenarios this change added. Leaves the pre-existing automation-designer/
automation-approval-steps scenarios untouched — gate-19 flags them because
editing shared requirement bodies pulls the whole file into diff scope,
not because this PR modified their behaviour; backfilling e2e coverage for
already-shipped, unrelated functionality is out of this change's scope.
@rubenvdlinde
rubenvdlinde merged commit 4e83434 into development Jul 24, 2026
2 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