Skip to content

Latest commit

 

History

History
568 lines (436 loc) · 29.3 KB

File metadata and controls

568 lines (436 loc) · 29.3 KB

k.LAB Components

Components are the extension mechanism used by k.LAB services to add capabilities without rebuilding the service that hosts them. A component is a PF4J plug-in that implements the k.LAB component contract and is packaged as a .kar archive by the k.LAB Maven packaging goal. The .kar is intended to be a stripped, dependency-free archive: it contains the component code and metadata, while common k.LAB and service dependencies are supplied by the hosting service.

Once imported, a component is registered in the service's local component registry. The registry loads the archive, scans it for k.LAB extension annotations, builds a descriptor, publishes the new capabilities through the service, and keeps enough source information to support maintenance and optional updates.

Repository Layout

Each service keeps its own component repository. In the standard service data layout this is under:

services/<service-type>/components/

The repository contains three separate concerns:

components/
  catalog.json
  history.jsonl
  cache/
    catalog.json
    ...
  plugins/
    *.jar

plugins/ is the only directory scanned by PF4J. Imported .kar files are copied or installed there with a .jar extension, because PF4J loads Java archives. Keeping loadable archives under plugins/ prevents unrelated repository files, including the Maven cache, from being interpreted as plug-ins.

cache/ is used for Maven-sourced artifacts. It records the component archive discovered in the local Maven repository or downloaded from a remote repository, together with content hashes and timestamps used to decide whether an update is needed.

catalog.json is the registry's local component catalog. It records the descriptors for components known to the service, including the source archive, source hash, Maven coordinates when applicable, usage rights, exported capabilities, and the time of registration or update.

history.jsonl is an append-only service-local audit trail. Each line is an independent JSON event, so a damaged final write does not invalidate the earlier history. Events identify the component and version, hosting service, import type, source service, outcome, message, and any source-specific details.

When an older repository contains loadable .jar files directly in the repository root, startup migrates them into plugins/ when possible. Files that are not plug-ins should remain outside plugins/.

What A Component Can Add

A component can contribute several kinds of service-visible functionality. A single component may bring any combination of these.

Libraries

A library is a namespace for k.LAB service verbs and language extensions. It can define:

  • k.IM, observation-language, or runtime functions through @KlabFunction.
  • k.Actors verbs through @Verb.
  • k.LAB annotations and handlers through @KlabAnnotation.
  • Import schemata through @Importer.
  • Export schemata through @Exporter.

Library names become part of the public name of the contributed functions, verbs, annotations, importers, and exporters. This keeps component contributions distinguishable from core k.LAB extensions and from other components.

Actors

Actors are specialized libraries for k.Actors use. They can expose singleton actors or actor classes that behaviors import with a using clause. Actor classes may also define verbs that apply to the actor. Component actors should use explicit path-like names so they remain unique outside the core distribution.

Resource Adapters

Resource adapters teach a Resources service how to understand, validate, contextualize, publish, inspect, or encode a class of resources. A resource adapter declares a unique adapter name, version, supported geometry, resource type, parameter schema, concurrency model, and optional runtime constraints such as split behavior or maximum geometry size.

Adapter methods can implement lifecycle operations such as:

  • URN syntax validation.
  • Local import and staging validation.
  • Pre-contextualization checks and cache preparation.
  • Encoding and contextualization.
  • Inspection, sanitization, publication, and type attribution.

Adapters may be embeddable or service-bound. An embeddable adapter can be used locally by a runtime close to the data. A service-bound adapter must be invoked through the Resources service that provides it, which is appropriate when the adapter depends on server-side state or protected infrastructure.

Authorities

Authorities connect the Reasoner to external terminologies and classifications that are too large or too specialized to load into a worldview in full. Component classes implementing org.integratedmodelling.klab.api.services.Authority are discovered through the Java @Authority annotation. Their descriptors advertise a stable provider URN, whether the provider is embeddable, and any known sub-authorities.

Resources services advertise and deliver authority components but do not host authority implementations. A Reasoner instantiates them. Embeddable providers may be transferred from Resources and installed by a Reasoner on demand; non-embeddable providers are available only on Reasoners where their component was explicitly installed. A worldview binds the provider URN to a local name and configuration, independently for each requires authority declaration. The full provider, binding, and reasoning contract is documented in Authorities.

The intended source integrity rule limits authority offerings to the certified Resources services contributing to the loaded worldview, including authorized higher-tier contributors. Generic component distribution by an ordinary Resources service does not confer that role. Enforcement and an explicit full-local development association remain pending; see worldview provider integrity.

Import And Export Schemata

Importers and exporters describe transport formats for k.LAB assets. They may be defined in a library or attached to a resource adapter. A schema can be based on binary content, such as a file or stream, or on named properties.

The built-in component I/O library currently defines:

  • component.kar.import: import a component by uploading a .kar archive.
  • component.maven.import: import a component from Maven coordinates.
  • component.jar.export: export an installed component archive.

Other components can add domain-specific import and export schemata for resources or other assets.

Runtime Services

@KlabFunction contributions become executable service implementations. Depending on their signature and metadata, they may serve as contextualizers, filters, producers, value functions, or other runtime functions used by k.IM, k.Actors, and the observation language. Their descriptors include declared parameters, inputs, outputs, geometry constraints, artifact type, fill curve, and parallelization hints.

Import Paths

Direct .kar Import

A direct import uploads a component archive to a service. The registry validates that the archive looks like a Java archive with a manifest, copies it into plugins/, loads it with PF4J, and scans it for k.LAB contributions.

Direct imports are intentionally local. They do not record Maven coordinates, so the registry cannot automatically rediscover a newer build. The hosting service therefore reports the imported archive as up to date until a replacement .kar is uploaded. Updating a directly imported component means uploading that replacement or importing a newer version. Services that copied the component as a dependency can still discover the replacement through the hosting Resources service's component descriptor.

Maven Import

A Maven import identifies the component with:

groupId:artifactId:version

The registry asks the Maven component cache for the artifact with classifier component and suffix kar. The cache first looks in the local Maven repository, then in configured remote Maven repositories when needed. The resulting .kar is cached, installed into plugins/ as a .jar, loaded with PF4J, and registered with its Maven coordinates.

Maven-sourced components keep their coordinates in the component descriptor. That source information is what enables later update checks.

Service-to-Service Transfer

An installed component can be exported as a Java archive and imported by another service. A component resolved from a Resources service is recorded as a DEPENDENCY, including that source service id and timestamp. When known, Resources also transports the component's Maven coordinates as provenance. Those coordinates permit a local Maven override; they do not turn the dependency into an independently Maven-managed import. A manually exported archive uploaded without that provenance is a direct local FILE import instead.

The four acquisition paths and their update owners are therefore:

Acquisition path Installed type Update owner
Local .kar uploaded to a Resources service FILE An administrator uploads a replacement.
Local Maven repository MAVEN The importing service's Maven cache checks a changed local artifact for a SNAPSHOT.
Remote Maven repository MAVEN The importing service's Maven cache checks remote SNAPSHOT metadata and downloads when required.
Local or remote Resources service DEPENDENCY The consumer first accepts a newer same-version SNAPSHOT from its local Maven repository; otherwise the advertising Resources service is authoritative.

The Resources host may itself have acquired the component through any of the first three paths. A dependent service never queries or downloads from a remote Maven repository. Its complete priority order is: a newer local Maven SNAPSHOT, then the descriptor and archive installed at the exact advertising Resources service. Thus a developer's local mvn install takes precedence, while in all other cases Resources remains authoritative even if a newer remote Maven artifact exists.

Registration And Discovery

After PF4J loads a component, the registry scans it and builds a ComponentDescriptor. The descriptor records:

  • The component id, version, and description from the plug-in descriptor.
  • The local source archive and its file hash.
  • Maven coordinates, when the component came from Maven.
  • Usage rights inferred from the plug-in manifest.
  • Libraries, actors, adapters, authorities, services, annotations, verbs, importers, and exporters.
  • The source service id, when applicable.
  • The registration or update timestamp.
  • The import type: BUILT_IN, FILE, MAVEN, or DEPENDENCY.
  • The last computed update status and timestamp of the latest version known at the source.

The update status is one of NOT_UPDATEABLE, UNKNOWN, UP_TO_DATE, or UPDATE_AVAILABLE. latestVersionTimestamp is zero when the source cannot provide an authoritative timestamp. These fields are computed for advertised descriptors and do not alter or install components. Stable Maven releases and the built-in service component are not updateable; Maven SNAPSHOTs, directly imported .kar archives, and dependency copies are updateable through their respective sources.

The descriptor is saved to catalog.json and indexed by contribution type. Service verbs, adapters, authorities, annotations, verbs, importers, and exporters can then be resolved by name. Authorities are indexed by their provider URN; the worldview-local name is a separate Reasoner binding and is not a component-registry key. When more than one descriptor can satisfy a request, the registry prefers the highest version compatible with the requested version. Exact version requests are matched exactly.

The registry also has a local service component. This makes built-in service libraries, adapters, and authorities visible through the same lookup mechanism used for imported components.

Component History

Each service records the lifecycle and synchronization events that affect its own copy of a component. This applies equally to primary FILE or MAVEN installations and to DEPENDENCY copies held by secondary services. Recorded events include registration, discovery of a newer build, update start and completion, unload, deferred restart installation, rollback, and failures. Routine update checks that find no new build are deliberately not recorded.

Events produced after an update candidate is selected include a machine-readable decision and policy in details (for example LOCAL_MAVEN_PRECEDENCE or RESOURCES_AUTHORITY and LOCAL_MAVEN_THEN_RESOURCES). This makes the selected source and its rationale visible in the IDE History dialog. The typed event retains service IDs for correlation and also carries user-facing service names when the service is visible; clients should display the name and use the ID only as a fallback.

History is exposed as a typed projection through the generic service info API. A client can use:

service.info(
    componentId + "@" + componentVersion,
    KlabAsset.KnowledgeClass.COMPONENT,
    ComponentHistory.class,
    userScope);

Omitting @version returns events for all retained versions of that component. Events are returned newest first. The same visibility check used for component descriptors is applied before history is returned, so an IDE can fetch this projection when a ComponentCard history action is opened without a separate administration endpoint.

Versioning And Compatibility

Component versions are independent from the k.LAB service version, but they must declare whether they can run with the hosting k.LAB version. Compatibility is controlled through the plug-in manifest's PF4J version requirement, conventionally Plugin-Requires.

At startup the component manager sets the PF4J system version to the current k.LAB version. During update checks, a candidate replacement archive is opened before installation and its version requirement is checked against the current k.LAB version. If the requirement is absent, blank, or *, the archive is accepted as compatible. If it declares an incompatible requirement, the update is skipped and the existing component remains installed.

For predictable maintenance:

  • Use stable semantic versions for released components.
  • Use -SNAPSHOT only for components that are expected to change in place.
  • Set Plugin-Requires narrowly enough to prevent loading against incompatible k.LAB releases.
  • Keep the PF4J plug-in id stable across versions of the same component.
  • Use unique names for libraries, actors, adapters, authorities, and service verbs.

Side-by-side descriptors for multiple component versions can exist in the catalog, but PF4J plug-in ids are unique in the running plug-in manager. In practice, the runtime should be maintained as one active version of a plug-in id at a time, with the registry selecting the best compatible descriptor for lookups.

Update Modes

Components can be updated manually or automatically. Scheduled repository polling applies only to Maven-sourced SNAPSHOT components. Dependency copies are checked on demand when a secondary service resolves a library service call, Java actor, adapter, or authority contribution.

Update discovery follows the component's import type:

  • A hosted MAVEN SNAPSHOT checks the Maven component cache and configured repositories. Its latest timestamp comes from the selected local artifact or remote snapshot metadata.
  • A hosted FILE component has no external repository to poll. Its current registration timestamp is also its latest-known timestamp; uploading a replacement advances both.
  • A DEPENDENCY component with SNAPSHOT coordinates first checks only the local Maven repository. A different, newer local artifact makes its status UPDATE_AVAILABLE. If there is none, it asks the ResourcesService identified by sourceServiceId for the matching hosted descriptor. A newer hosted registration or an update advertised by that service then makes the dependency's status UPDATE_AVAILABLE. Remote Maven repositories are never consulted by this path.
  • BUILT_IN components and stable Maven versions report NOT_UPDATEABLE.

This separation is important operationally: a local Maven install is an explicit developer override. Otherwise the Resources service owns and serves hosted components, and Runtime, Reasoner, and other secondary services refresh dependency copies from that Resources service.

Dependency Refresh During Resolution

Before resolving a library service call, Java actor, adapter, or authority from a dependency component, the consuming service first checks for a newer local Maven SNAPSHOT. If none is selected, it checks whether the exact source Resources service is currently visible and compares the installed timestamp of the matching source descriptor with the timestamp of its local dependency copy. Only a source component that is already installed with a newer timestamp triggers replacement; an upstream update merely advertised by the source is not copied until the source has installed it.

Runtime services use this scope-aware lookup before compiling or executing a component-provided ServiceCall reactor and before resolving a Java actor or agent adapter. Replacement therefore precedes selection of reflective methods and classes, avoiding new executions retaining a descriptor from the older build. Objects and agents already executing from the old class loader are not migrated; update them during a quiet maintenance window when stateful implementations are in use.

When the contribution is not installed at all, the consumer resolves its full ServiceCall URN as SERVICE_IMPLEMENTATION through the scope's merged Resources client. A current Resources service must return the embeddable COMPONENT descriptor that provides the implementation. After transfer, the Runtime verifies that the requested function is registered and adds a SERVICE_IMPLEMENTATION result to the requirements returned to the Resolver; this is what makes the newly available prototype visible while the resolution graph and dataflow are compiled. All services participating in a stack should therefore run the same version of this discovery contract.

Resource URNs have a separate adapter-discovery path. A universal resource has the form klab:<adapter>:...; it contains no stored data and therefore has no resource-catalog entry. When Runtime resolves such a RESOURCE and the named adapter is not installed locally, it first uses any COMPONENT dependency returned with the resource. If none was returned, it explicitly resolves the second URN element as a RESOURCE_ADAPTER through the merged Resources client. That resolution returns the providing COMPONENT, which is transferred and installed before dataflow compilation continues. Runtime then verifies that the component registered an embeddable adapter with the requested version. The synthetic RESOURCE descriptor is retained in the resolution result, but it is not passed to catalog retrieval: the now-local adapter synthesizes the Resource when the compiled dataflow uses it.

Embeddability is an execution choice, not a condition for resolving a universal resource. When the hosting Resources service advertises a non-embeddable adapter, Runtime retains the synthetic RESOURCE descriptor and its source service ID without requesting the component. The compiled dataflow retrieves the synthesized Resource from that service and selects RemoteAdapterExecutor, which obtains data through the Resources contextualization/Avro transport route. Component synchronization is used only for an adapter advertised as embeddable.

An install or actual same-version build replacement sends informational start and completion notifications through the initiating scope. Runtime messaging subscribes to the Info queue by default, so these notifications can be displayed by the submitting client during a longer submission, resolution, or contextualization cycle. Checks that find no newer build remain silent and are not added to component history.

The source service is not required to be present when the consumer initializes. If it is absent, resolution continues with the local copy and the same check is made again on later resolutions. When a newer source installation is available, the replacement archive is exported and validated before the consumer attempts to unload the active component. A successful replacement is registered and started before resolution continues. The registry preserves or restores the installed component whenever possible if replacement fails. A scope-aware library service-call or Java actor lookup then rejects the request instead of selecting that known-stale component; update-check APIs that do not immediately consume the contribution simply report that no replacement was installed.

If the validated replacement cannot be installed immediately, for example because Windows still holds the active archive open, it is saved in the component repository's pending-updates/ directory. On the next service restart it is copied into plugins/ before PF4J initializes and its dependency provenance is restored from the local marker. This startup operation is entirely local and does not depend on the source Resources service being available. If export or validation fails before an archive can be staged, no restart action is possible and a later resolution retries the source check instead.

Manual Direct Update

Upload a new .kar archive with component.kar.import, or use the service path that verbs the same importer. This is the right path for local development builds, private hand-offs, and components that are not published to Maven.

The update is local to the service that receives the archive. The registry loads the replacement, rescans its contributions, updates the descriptor, and saves the catalog.

Manual Maven Update

Import the desired Maven version with component.maven.import, or call the registry's Maven installation path. This works for stable releases and snapshots.

For stable releases, prefer publishing a new version and importing that explicit version. Stable versions are not expected to change in place, so they are not part of the automatic SNAPSHOT update loop.

Explicit SNAPSHOT Check

ComponentRegistry.checkForUpdates() performs a read-only update check for Maven-sourced SNAPSHOT components. It walks the registered components and considers only descriptors that have Maven coordinates whose version identifies a SNAPSHOT build. It does not synchronize an artifact, unload a component, or install a replacement.

For each candidate, the Maven cache determines whether:

  • The cached artifact is up to date.
  • A different artifact exists in the local Maven repository.
  • A newer snapshot appears to exist in a configured remote repository.
  • The status cannot be established.

The result reports available updates and diagnostics. The component descriptors advertised by the service provide the structured updateStatus and latestVersionTimestamp used by administration clients.

SNAPSHOT Update Actions

ComponentRegistry.updateMavenSnapshotComponents() composes discovery with updates for all changed Maven SNAPSHOTs, preserving the existing automatic behavior. updateComponent(id, version, scope) is the service-aware targeted action: for dependencies it applies the same local-Maven-then- Resources precedence used during resolution. When an update is indicated, these actions synchronize the artifact, compare the candidate file hash to the installed descriptor hash, verify k.LAB compatibility, unload the current component, install the replacement archive, register the new descriptor, and save the updated catalog.

Administrators invoke targeted update and removal through the common settings API by posting a Map to UPDATE_COMPONENT or REMOVE_COMPONENT. Both maps require component and accept version; their result map contains result, message, action, component, and version. The settings controller independently enforces CRUDOperation.ADMINISTER, and the IDE enables the matching ComponentCard actions only when the target service advertises that permission. Removal of the built-in service component is rejected. Explicit removals are recorded in component history with an ADMINISTRATOR_SETTING trigger; update events retain the source-selection rationale described above. A no-op update request that finds no newer build is not added to history.

Hash comparison is important because SNAPSHOT timestamps and repository metadata can be noisy. If the retrieved archive has the same content hash as the installed archive, the registry does not replace the component.

Startup Update

Services can request a one-time check and update after component registry initialization. The startup option is:

-updateComponents

The current service startup options enable this by default. Implementations that only use the StartupOptions interface inherit the same default unless they override it.

Periodic Automatic Update

Services can also schedule repeated SNAPSHOT checks and updates:

-autoUpdateComponents
-componentUpdateIntervalMinutes <minutes>

The interval defaults to five minutes and is clamped to at least one minute. Scheduled checks run quietly: successful updates are logged, and failures are reported through service logging without stopping the service.

Periodic updates should be enabled deliberately. They are useful for controlled development and integration environments where SNAPSHOT components are expected to move. Production services should normally prefer explicit stable versions or an explicit maintenance operation.

Cache Behavior

The Maven cache keeps a small catalog for each synchronized artifact. Its entries include the Maven coordinates, cached file, content hash, and last-modified time. The update strategy is:

  1. Prefer the local Maven repository when the requested artifact exists there.
  2. Compare the local artifact hash with the cached hash.
  3. For SNAPSHOT versions not available locally, check remote snapshot metadata.
  4. Download or copy only when the cache says an update is needed.
  5. Keep the previous cached artifact if retrieval fails.

This makes local development fast: installing a new SNAPSHOT into the local Maven repository is enough for the next explicit or scheduled check to notice a content change.

Operational Guidance

Component loading is hot-swappable, but the practical safety of replacing a component depends on what is using it. Updating a component that is actively serving contextualization, resource validation, or long-running operations can leave callers with references to classes from the old plug-in. Windows file locking can also prevent immediate replacement of archives in some cases.

For low-risk operation:

  • Update during service startup or a quiet maintenance window when possible.
  • Prefer explicit update checks over frequent automatic checks in production.
  • Keep component archives dependency-free and small.
  • Do not put non-component files in plugins/.
  • Treat imported components as trusted code. Manifest usage rights govern resource visibility and sharing, but they are not a security sandbox.
  • Keep stable releases immutable. Publish a new version instead of replacing an old release.
  • Use SNAPSHOT automatic updates only where changing artifacts in place is acceptable.

Packaging Checklist

For a new component, use the org.integratedmodelling:klab.component.archetype archetype rather than assembling the PF4J and packaging boilerplate by hand. It always creates the required KlabComponent entry point and can independently add compilable starters for libraries and contextualizers, agents, resource adapters, importers/exporters, and authorities. The complete command, property reference, build workflow, and Central deployment setup are documented in Maven Builds And Component Archetype.

A component archive should include:

  • A unique PF4J plug-in id that is also meaningful as the component id.
  • A component version.
  • A plug-in class implementing the k.LAB component contract.
  • A human-readable description.
  • A provider or vendor identity when available.
  • A license or rights declaration that can initialize usage rights.
  • A Plugin-Requires constraint for the compatible k.LAB version range.
  • Only the classes and resources needed by the component itself.
  • The project license; generated components include GNU Affero GPL version 3 by default.

The Maven artifact intended for component import should be published with classifier component and suffix kar, so the registry can resolve it as:

groupId:artifactId:version:component:kar

The import-facing coordinate remains the simpler:

groupId:artifactId:version

Minimal Workflows

Install a component from a local archive:

component.kar.import(file = my-component.kar)

Install a component from Maven:

component.maven.import(
  groupId = org.example,
  artifactId = my-component,
  version = 1.2.0
)

Install a development SNAPSHOT and let the service check for newer local or remote builds:

component.maven.import(
  groupId = org.example,
  artifactId = my-component,
  version = 1.3.0-SNAPSHOT
)

Then run the explicit bulk or targeted update action from the hosting service, or start the service with periodic updates enabled:

-autoUpdateComponents -componentUpdateIntervalMinutes 10

The automatic path will update only if the component is Maven-sourced, its version is a SNAPSHOT, the retrieved archive hash differs from the installed archive hash, and the candidate archive is compatible with the current k.LAB version.