Skip to content

feat(intent): cross-model schedule source (model: on schedules + forEach) - #6532

Open
delchev wants to merge 1 commit into
masterfrom
feat/intent-cross-model-schedule-source
Open

feat(intent): cross-model schedule source (model: on schedules + forEach)#6532
delchev wants to merge 1 commit into
masterfrom
feat/intent-cross-model-schedule-source

Conversation

@delchev

@delchev delchev commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What

A P6 schedule's source entity — and, inside a scheduled generate, a forEach collection — may now live in another intent model, referenced by a new model: <uses alias> key. Omitting model: keeps today's behaviour (local source), and existing generations stay byte-identical.

uses:
  - { model: projects }
schedules:
  - name: monthlyProjectTimesheets
    cron: "0 0 2 1 * ?"
    entity: Project
    model: projects                # NEW: the source entity's model alias
    where: [ { field: Status, op: eq, value: 2 } ]
    generate:
      to: ProjectTimesheet         # LOCAL — no uses: needed
      map: { Project: id }
      children:
        - to: EmployeeTimesheet
          parent: ProjectTimesheet
          forEach:
            entity: EmployeeProjectAssignment
            model: projects        # NEW: a cross-model forEach collection
            match: { Project: id }
          map: { Employee: Employee }

Why

Today a schedule's source must be a local entity, so the module that owns the created rows cannot own the schedule — it is forced into the source's module with a uses: back-edge, a dependency in the wrong direction (and a leaf-first-generation / bootstrap headache for any pair of mutually-referencing models). Letting the consumer own the schedule and read the source cross-model removes that inversion. This is the existing consumer→owner uses: direction — a consumer's generated code already binds to an owner (PROJECTION entities, leafOnly validation), so no new coupling is introduced.

How

  • ScheduleIntent.model — new field (Gson-mapped YAML key).
  • Parsermodel: must name a declared uses: alias (parse); for a cross-model source the local entity/field checks are skipped and validated later; a cross-model source with notify is rejected (v1 scope — notify needs the source's relation metadata, which only a local entity carries); forEach.model handled the same way.
  • Glue generator — resolves the source through CrossModelSupport (workspace .model first, registry fallback, loud failure) — the same two-tier resolution relations / dependsOn / leafOnly already use. Every where / map / match reference is validated at generation against the owner's properties (skipped on the convention fallback, like dependsOn); a miss drops that schedule with a warning rather than emitting code that cannot compile. Emits sourceCrossModel/sourceModel (+ forEachCrossModel/forEachModel), every existing glue key preserved.
  • generateUtils.js + Job.java.template — the source (and cross-model forEach) imports resolve against the owner's gen.<owner>.data… package (the leafOnly precedent — the job only READS the source). sourceGenFolder defaults to the local folder, so local schedules regenerate byte-identically.
  • Intent editor — a cross-model schedule card renders with the qualified label and no edge.
  • Docsintent-assistant-guide.md; plus the two doc sites (see below).

Validation split (mirrors relations / dependsOn / leafOnly)

Check When
model: names a declared uses: alias parse
notify + cross-model source (rejected, v1) parse
source entity exists in the owner's .model generation (loud drop + warning)
where/map/match fields exist on the owner generation (skipped on convention fallback)

Scope

v1 is generate-action schedules only; the cross-model source is read-only (a schedule never writes it — one-writer-per-state preserved). Out of scope: notify over a cross-model source (needs NotificationSupport over TargetInfo, no driving case), and any write to the source.

Tests

  • UnitIntentParserTest (declared-alias / undeclared-alias / notify-rejected / cross-model forEach) and GlueSchedulesTest (local stays not-cross-model; cross-model source emits the source + forEach keys and the Criteria). Full engine-intent suite green (308).
  • ITIntentCrossModelScheduleSourceIT (two mutually-referencing modules, HTTP-only): asserts the generated Job tokens (owner-package source import, Criteria, owner-package forEach FQNs) and the OUTERMOST layer — publish both, force-sync, seed the source over REST, and assert the cron-fired job created the target + child rows over REST — plus both loud-failure paths (owner .model absent; a where-field typo reported naming the owner model).
  • Regression: IntentEngineIT (36) + IntentEmissionCoverageIT (1) green — local-source Job emission unchanged.

Docs

Vendor-neutral spec (intentfile.org) and branded (dirigible.io /help/intent/) updated in the same effort.

Notes / deviations

The /parse endpoint receives only raw YAML, so — exactly as for relations, dependsOn and leafOnly — cross-model reference resolution happens at generation, not parse. No /parse signature change was needed.

…ach)

A schedule's source entity - and a generate forEach collection - may live in
another intent model, referenced by `model: <uses alias>`. This lets a schedule
live with the module that owns the CREATED rows instead of being forced into the
source's module with a dependency in the wrong direction.

- Omitted `model:` = local, fully backward compatible (existing generations stay
  byte-identical: `sourceGenFolder` defaults to the local gen folder).
- The generated JobHandler imports the OWNER's `gen.<owner>.data...` classes and
  only READS them (the leafOnly precedent); one writer per state is preserved.
- v1 scope: the generate action only. A cross-model source with notify is
  rejected at parse (notify needs the source's relation metadata).
- Validation split (mirrors relations / dependsOn / leafOnly): the `model:` alias
  must be a declared `uses:` (parse); the source entity and every where/map/match
  field reference are validated at generation against the owner's .model, via
  CrossModelSupport (workspace first, registry fallback). An unresolvable owner or
  a mistyped field drops the schedule with a warning - never code that can't compile.

Includes IntentParser + GlueIntentGenerator unit tests and a two-module HTTP-only
IT (IntentCrossModelScheduleSourceIT) asserting the generated Job tokens, the
running-instance row creation over REST, and both loud-failure paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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