Skip to content

Latest commit

 

History

History
462 lines (363 loc) · 30.1 KB

File metadata and controls

462 lines (363 loc) · 30.1 KB

Resources service contract

For optional semantic checks after parsing, see the reasoner's document validation API and client orchestration.

Project-owned metadata and the settings-only project replacement protocol are described in Default observers.

This document defines the resource-management contract exposed by ResourcesService. Its public asset API consists of resolve, retrieve, delete, list, and submit, plus the shared KlabService.info and KlabService.query inspection operations. Asset-specific methods are implementation helpers, not service API.

The other methods still declared by ResourcesService are operational facilities (for example, resource contextualization, repository management, behavior parsing, and project locking). They do not create an alternative asset CRUD surface.

Terminology: asset versus Resource

In this document, asset is the generic word for any object implementing KlabAsset: projects, namespaces, models, workflows, components, and the other managed knowledge objects are all assets. Resource with an initial capital denotes only the concrete org.integratedmodelling.klab.api.knowledge.Resource contract. A Resource describes an adapter-backed dataset or computation, including its URN, version, geometry, parameters, metadata, files, validation notifications, and history. “Resources service” is the proper name of the service, not a claim that every asset it manages is a Resource.

The publication lifecycle in this document applies to that concrete Resource contract. It must not be generalized to arbitrary assets without defining their own submission and review rules.

Core concepts

Every managed object is a KlabAsset, identified by a URN and classified by KlabAsset.KnowledgeClass. The class is part of the request: identical text can name different kinds of asset, and implementations must not guess when the caller can state the class.

Workspace-managed documents normally use their language URN. A project is addressed by its project name for retrieval and deletion. Submission of a new project uses workspace/project, because the target workspace is otherwise ambiguous. A workspace is addressed by its workspace name.

Except for public discovery, calls carry a UserScope. The scope is the authority for visibility, ownership, and mutation. Passing null is valid only for public assets, notably worldview discovery during service startup. Full privilege enforcement is not implemented in every provider branch yet; those branches are explicitly listed under "Work remaining".

ResourceSet is the resolution and change-set envelope. A resolution result contains descriptors for the requested asset and the dependency closure needed to use it, plus service locations and notifications. A mutation returns one ResourceSet for each affected workspace. An empty result is represented by ResourceSet.isEmpty() and may carry explanatory notifications; callers must not interpret an empty Java collection and an error-bearing empty ResourceSet as equivalent.

The resource operations

Method HTTP endpoint Contract
resolve(urn, knowledgeClass, scope) GET /api/v1/resolve/{knowledgeClass}/{urn} Locate an asset and return a self-contained ResourceSet, including required dependencies. This is discovery, not object transfer.
retrieve(urn, assetClass, scope) GET /api/v1/retrieve/{knowledgeClass}/{urn} Return the full serialized asset of the requested Java class, or null when it is not found or visible. Call resolve first when dependency loading matters.
delete(urn, knowledgeClass, scope) DELETE /api/v1/delete/{knowledgeClass}/{urn} Remove the asset and return change sets for all affected workspaces. Deletion is idempotent only where the underlying storage operation is idempotent.
list(assetClass, scope) GET /api/v1/list/{knowledgeClass} Return all visible assets of exactly the requested class. No filtering or descriptor conversion is implied.
info(urn, knowledgeClass, infoClass, scope) GET /api/v1/info/{knowledgeClass}/{urn}?infoClass=... Shared KlabService operation returning a projection or descriptor. Resources owns projections for its managed asset types and delegates common service/component projections to BaseService.
submit(asset, mode, scope) PUT /api/v1/submit/{knowledgeClass}/{submissionMode}/{urn} Add or change a typed asset and return workspace change sets. The body is the asset; the path class and URN must agree with it.
query(parameters, knowledgeClass, infoClass, scope) POST /api/v1/query/{knowledgeClass}?infoClass=... Shared KlabService operation selecting objects and returning one requested projection per match. For resource-managed types, an empty map is equivalent to listing the class followed by projection.

HTTP knowledge-class and submission-mode values are enum names. infoClass is the canonical Java class name and is required by info and query. Invalid classes, incompatible projections, and unsupported query keys are request errors; they must not silently broaden a query.

Resolve versus retrieve

resolve answers "what must be available to use this asset?" Its result contains lightweight descriptors and dependencies and can be merged across services. retrieve answers "give me the asset definition" and returns one concrete object from one service. A typical remote flow resolves, loads the returned dependency set, and then retrieves concrete assets from the services named in the result.

The provider currently resolves resources, namespaces, ontologies, behaviors, models, projects, workspaces, worldviews, components, and service implementations. Special informational resolution identifiers are:

  • export-schema:<media-type> and import-schema:<media-type> with INFORMATION;
  • an adapter identifier, optionally versioned, with RESOURCE_ADAPTER;
  • <service-call-urn>@<version> with SERVICE_IMPLEMENTATION.

Model resolution through query uses the typed convention {"observable": <Observable>}, KnowledgeClass.MODEL, and ResourceSet.class. This preserves the old semantic model-candidate operation while keeping it under the generic query API.

Universal resources

A resource URN whose node (first element) is klab, such as klab:random:..., is universal. It does not identify stored data and is intentionally absent from every resource catalog. Its catalog (second element) is instead the adapter identifier. Resolving it as RESOURCE returns a synthetic RESOURCE descriptor and, when available, the embeddable COMPONENT that supplies that adapter.

A consuming Runtime must not interpret the RESOURCE descriptor as a catalog object. If the adapter is missing and the RESOURCE dependency set did not carry its component, Runtime resolves the adapter identifier separately as RESOURCE_ADAPTER through the merged Resources client. The result identifies the providing COMPONENT, which Runtime installs before verifying the embeddable adapter. Retrieval is deferred until the compiled dataflow uses the URN; at that point any Resources service with the adapter can synthesize the Resource. Ordinary non-klab resource URNs retain the owner-specific resolve-then-retrieve contract. Adapter descriptor retrieval and queries use RESOURCE_ADAPTER; the former INFORMATION projection is not part of this contract.

The same adapter lookup is also a compatibility fallback when the merged RESOURCE lookup is empty. If it installs an embeddable adapter, Runtime synthesizes the universal RESOURCE descriptor locally and continues resolution. This lets a newer Runtime consume an adapter advertised by a Resources service that does not yet implement synthetic universal-RESOURCE resolution.

A universal adapter need not be embeddable. For a non-embeddable adapter, the RESOURCE descriptor's service ID identifies the Resources service that will synthesize and execute it. Runtime does not request or install that component; dataflow compilation retains a remote adapter descriptor and execution uses the Resources contextualization API and Avro data transport. Thus universal-resource resolution supports both local embedded execution and remote hosted execution.

Retrieve and list

WorkspaceManager is the source of full workspace-managed assets. It retrieves and lists workspaces, projects, namespaces, ontologies, observation-strategy documents, behaviors, models, and symbol definitions. The provider additionally retrieves resources, concepts, observables, and the served worldview.

Worldview retrieval currently supplies a container from one provider; it does not establish a complete protocol for assembling certified higher-tier contributions from several services. The intended production rule also restricts authority plug-ins to the authorized services supplying the loaded worldview. Ordinary Resources providers must not acquire that role through general component distribution. These source restrictions are not yet enforced. See worldview composition and authority provenance for the proposed distributed contract and full-local development requirements.

list has no search semantics. Clients that need matching, sorting, or a different representation must use query. In a multi-service scope, ResourcesMerger snapshots all resource services, including Resources clients advertised in a service-side scope even when they are temporarily absent from its live status-filtered typed projection. It de-duplicates the snapshot by service ID (or URL when no ID is known), excludes itself, queries the services concurrently, tolerates an individual failure, and returns distinct results in service order. This prevents a cached merger from silently becoming local-only while the advertised federation still contains a remote provider. retrieve, writes, and operational calls go to the primary service because their results cannot be combined safely.

Info

info and query belong to KlabService, so every service exposes the same transport and typed projection contract. BaseService supplies common inspection of installed components, adapters, service implementations, service capabilities/status, and portable DomainObject descriptors. ResourcesProvider retains exclusive knowledge of resource-managed asset classes and delegates a request to BaseService when the requested projection belongs to that common contract.

If infoClass is compatible with the asset itself, info is equivalent to retrieve. String returns the matched URN, which lets query callers request identifiers without a separate listResourceUrns endpoint. Current provider projections also include:

  • ResourceInfo for catalog and authorization metadata;
  • ResourceTransport.Schema.LanguageDescriptor for a language identifier with INFORMATION.

AdapterDescriptor, component descriptors, and service-implementation descriptors are supplied by the common BaseService component-registry implementation and are therefore available from any service that has loaded the corresponding component.

Additional DomainObject projections should describe a stable schema suitable for API discovery. Unsupported projections fail explicitly. Model Coverage is reserved but not computed yet.

Query

The parameter map belongs to the selected asset class. Current general parameters are:

  • urn: a Java regular expression matched against the complete URN;
  • query: an alias for the general textual/URN pattern;
  • limit or maxResults: cap a ResourceInfo catalog query to 1–100 results;
  • includePublishedLocal: include retained local sources that have already been published; the default is false on a local Resources service;
  • observable: the typed model-candidate query described above.

An empty map selects all visible assets of the class. If the requested infoClass is the asset class, the result is equivalent to list. Otherwise each selected asset is transformed through info. ResourceInfo queries use the resource catalog. Any unsupported key is rejected with a TO BE IMPLEMENTED error rather than ignored.

Submit

Submission modes are:

  • ADD: create only; an existing asset is not overwritten;
  • REPLACE: replace the current representation;
  • UPDATE: update while retaining history when storage supports it;
  • MERGE: merge submitted content into the existing asset.
  • CREATE_OR_UPDATE: create an absent asset or save a newer version of an existing one;
  • PUBLISH: submit a concrete Resource to a remote authoritative service for tier-1 review.

The current provider can ingest Resource, create Workspace, create Project, and replace/update file-backed k.IM documents carrying both projectName and sourceCode. A new project asset must use the workspace/project URN. Resource updates preserve the previous current representation in the embedded history and require the submitted version to be newer. k.Actors document mutation, project/workspace update, and merge semantics remain pending and return explicit notifications.

Additional project material

ProjectMaterial is a binary-safe KlabAsset with knowledge class and project resource type ADDITIONAL_MATERIAL. It contains projectName, path (canonical project-relative path), content (byte[], Base64 in JSON), and service/metadata fields. Its URN is project/path. It is not a KlabDocument and is never parsed as k.IM or k.Actors source.

Use the existing generic submit, retrieve and delete methods. The client uses these query-coordinate routes so relative paths never depend on encoded slashes in a path segment:

  • PUT /api/v1/submit/ADDITIONAL_MATERIAL/{mode}?urn=project/review/notes.txt
  • GET /api/v1/retrieve/ADDITIONAL_MATERIAL?urn=project/review/notes.txt
  • DELETE /api/v1/delete/ADDITIONAL_MATERIAL?urn=project/review/notes.txt

The submitted body and query URN must agree. Modes are ADD (requires absence), UPDATE (requires existence), and CREATE_OR_UPDATE; REPLACE, MERGE and PUBLISH are rejected. The filename, including extension, is part of path; bytes are preserved without transcoding. core.project.write_text is the UTF-8 convenience operation.

Creation/update require service UPDATE_METADATA and access to the containing project (ownership or its existing user/group rights). Reading requires READ or UPDATE_METADATA. Deletion requires DELETE. Administrators retain their override. A user with UPDATE_METADATA alone cannot perform document CRUD, replace project settings, or delete material. Project API permission descriptors report these effective operation grants; actors enforce the same rules as the service.

Only service-owned FileProjectStorage is writable. Parent directories are created automatically; paths must already be canonical, use /, and remain within the project. Absolute paths, traversal, platform aliases, symlink traversal, language-document paths (including case variants), Git control files, META-INF, and managed resources paths are rejected even if the target does not yet exist. Thus additional material cannot replace canonical documents or bypass their permissions. Payloads are limited to 32 MiB. Ignored or conflicted Git paths fail explicitly.

Mutations stage the one affected path in Git without committing or pushing; unrelated staged changes remain intact. An unlocked project accepts metadata contributions without requiring UPDATE. A project locked by another user rejects mutations. Workflow checkpoints and project Git writes are separate persistence operations; retrying a workflow does not undo previous project effects.

Complete lifecycle of a concrete Resource

1. Local creation and tier 0

A new Resource is first created on a local Resources service. Its adapter contract, required parameters, geometry, identity, version, metadata, and local-file integrity are validated before a normal save is enabled. Creation establishes a ResourceInfo catalog record with owner-scoped rights, Stage.STAGING, and review status 0 (tier 0).

The editor distinguishes two subsequent mutations:

  • Update temporary data uses REPLACE. It keeps the same version and is accepted only by a local service or for a tier-0 record. It is intended for incomplete staging metadata and still requires a valid resource overview.
  • Save new version uses UPDATE. Full validation must pass, the version must be newer than the current version, and the former current representation is appended to Resource.history. Historical versions do not appear as separate browser results.

Permissions do not live in the serialized Resource. ResourceInfo.rights is independently persisted in ResourcesKBox, and the editor updates it through its dedicated permissions action. Saving resource content does not implicitly save permission edits.

Project settings use a different, combined save contract. WorkspaceEditor loads project-owned metadata and the catalog rights into one draft. A settings-only project REPLACE submission stores metadata in META-INF/manifest.json and an optional ProjectSettings.permissions update in the project's ResourcesKBox record. Null permissions leave the catalog unchanged; empty text means owner-only access. Owner and service grants are preserved. The requester needs service UPDATE plus project access (or service administration), and must own the project lock. The normal project setRights route has the same checks. Project ResourceInfo.permissions exposes effective UPDATE access for the settings UI. See the settings contract for error recovery and transport details.

2. Publication eligibility in the editor

Publication is a separate tab, not a variant of local Save or Update. It is available only when all of these conditions hold:

  • the source service is local and the local Resource is no longer an unsaved draft;
  • client-side validation has no errors;
  • at least one available non-local Resources service grants CRUDOperation.CREATE to the current UserScope;
  • the user explicitly selects and confirms the target, acknowledging that local editing becomes restricted.

Target discovery and submission run off the JavaFX application thread. The publication action is a WaitButton, so a long adapter validation or remote ingest exposes waiting/success/failure state without blocking the UI.

The caller is an editor when WorkflowParticipant.from(UserScope) contains WorkflowRole.EDITOR (workflow administrators also qualify). Otherwise the publication tab requires an intended editor identity and sends it as klab.publication.intendedEditor metadata. The remote endpoint rejects a non-editor submission without that metadata even if a non-IDE client bypasses the UI.

3. Remote submit and review entry

The client submits the complete Resource to PUT /api/v1/submit/RESOURCE/PUBLISH/{urn}. PUBLISH is invalid for any other knowledge class. The target enforces CREATE permission and rejects the request when:

  • the target is local;
  • any version of the URN already exists;
  • incoming validation notifications contain an error;
  • mandatory adapter parameters are missing;
  • the target adapter's local-import validator rejects the resource; or
  • a non-editor submission has no intended editor metadata.

The adapter validator or metadata analysis may replace the catalog and namespace components. Once that processing succeeds, the service replaces the first URN component with its own sanitized service name and returns that final authoritative URN. A duplicate check is repeated against the final URN, so analysis cannot accidentally overwrite an existing resource.

Successful ingest creates exactly one current resource keyed by that final URN. Its remote ResourceInfo is set to Stage.REVIEWING and review status 1 (tier 1, “under review”). The configured publicationReviewWorkflow defaults to asset-review. For an editor submission, the service tries to start that workflow automatically in its editing state. A missing or unusable workflow does not roll back the accepted resource; it is reported as a warning so review can be started manually. Setting the configuration value to blank disables automatic workflow creation.

4. Recording authority on the local source

Only after remote acceptance does the client call the source service's markPublished API. The source must itself be local, and only the resource owner or a service administrator may change the record. The endpoint is PUT /resourceInfo/{urn}/publication/{serviceId} and its request body carries the final authoritativeResourceUrn. The local ResourceInfo then stores:

  • published = true;
  • authoritativeServiceId equal to the accepting remote service ID; and
  • authoritativeResourceUrn equal to the final URN returned after remote validation and analysis;
  • publicationTimestamp for the successful handoff.

This is a cross-service two-step operation, not a distributed transaction. If the remote accepts the resource but the local catalog update fails, the editor reports that exact partial outcome; the remote copy is authoritative and the local record must be reconciled rather than submitting a duplicate blindly.

5. Discovery and local read-only behavior

Normal local ResourceInfo search omits records whose local source has published = true. includePublishedLocal=true opts into those records through the generic query API; the resource browser exposes the same option as Show published local resources. The filter applies only to a local service, so it never hides the authoritative remote record.

When an opted-in published local source is opened, its resource fields, Save, Update, and Delete actions are read-only by default. Edit published local copy is shown beside the local action buttons. Checking it opens an explicit warning that local changes do not update the authoritative server; only confirmation enables editing. Permissions retain their independent authorization and update action.

6. Re-publication and deletion

The authoritative service ID is retained even when that service is unavailable. Re-publication is allowed when the recorded authority is unavailable. If it is available, only a user with CRUDOperation.ADMINISTER may choose a different target, and the editor warns that normal operations belong on the authoritative service.

The recorded authoritative service is not offered again while it still returns the recorded authoritativeResourceUrn. It becomes eligible only after that remote resource has been deleted. Independently of UI discovery, every remote PUBLISH request rejects an existing URN, including a different version, so concurrent or stale clients cannot turn publication into an update.

Delete

Deletion is selected by knowledge class and delegates to the existing storage-specific helpers. Document, project, workspace, and resource removal return workspace change sets. The remote client currently cannot deserialize the response body of its generic HTTP DELETE helper, so it returns an empty Java list after a successful request; this transport limitation must be fixed before remote callers can react to deletion change sets.

Migration from specialized methods

Old operation Generic operation
resolveModel, resolveResource, adapter/service/schema resolvers resolve(urn, knowledgeClass, scope)
resolveModels(observable, scope) query(Map.of("observable", observable), MODEL, ResourceSet.class, scope) and merge returned sets
retrieveNamespace, retrieveOntology, retrieveBehavior, retrieveProject, retrieveWorkspace, retrieveResource retrieve(urn, AssetClass.class, scope)
retrieveWorldview list(Worldview.class, scope) or retrieve(advertisedUrn, Worldview.class, scope)
listWorkspaces, listProjects list(Workspace.class, scope), list(Project.class, scope)
listResourceUrns query(Map.of(), RESOURCE, String.class, scope)
resourceInfo, retrieveAdapterInfo, modelGeometry info(urn, knowledgeClass, Projection.class, scope)
queryResources query(parameters, knowledgeClass, ResourceInfo.class, scope)
create/update/register methods construct the typed asset and call submit(asset, mode, scope)
specialized delete methods delete(urn, knowledgeClass, scope)

Specialized methods that still encapsulate useful storage logic remain concrete helpers inside ResourcesProvider; they are not overrides and are not callable through ResourcesService. Client implementations contain only generic CRUD calls. Legacy controller routes and constants remain temporarily for server compatibility and should be removed after external clients migrate.

Non-CRUD service operations

The interface retains operations whose contract is not asset CRUD: resource contextualization and data production, parsing standalone documents from an arbitrary URL, repository operations, importing through transport schemas, publishing observations, and project locking. These operate on execution, transport, or transaction state and should not be forced into CRUD solely to reduce method count.

parseAsset(URL, Class<T>, UserScope) accepts exactly four standalone document contracts: KActorsBehavior, KimOntology, KimNamespace, and KimObservationStrategyDocument. It parses without registering the result in a workspace. Remote clients upload the URL contents together with the requested document class, so file URLs do not need to be accessible from the service host.

Work remaining

The following gaps are deliberate and must remain visible until supporting contracts exist:

  • enforce privileges consistently in every resolve, retrieve, query, submit, and delete branch;
  • implement model coverage for info(..., MODEL, Coverage.class, ...);
  • implement resource composition and caching for multi-URN retrieval;
  • implement import-schema resolution and geometry-aware schema selection in the generic identifier;
  • implement project/workspace update and MERGE submission;
  • support k.Actors document mutation in WorkspaceManager;
  • make workspace deletion atomic and report complete change sets;
  • return DELETE response bodies from ResourcesClient;
  • enrich the minimal common DomainObject projection with stable type-specific schemas;
  • replace public startup calls with an explicit public-scope contract instead of relying on a null scope;
  • migrate and then remove legacy resource controller mappings and ServicesAPI constants.

The sibling klab-ide audit covered its resource-service call sites as well. Its standalone k.Actors parsing in AgentView and BehaviorEditor now calls parseAsset(url, KActorsBehavior.class, scope). Its workspace and resource views use the generic retrieve, query, info, and typed submit operations. Calls from KlabIDEController into the modeler's own create/update methods are not ResourcesService calls and remain valid; the modeler implements those workflows through the generic service API where supporting payloads exist.

Workspace settings

Workspaces are virtual containers. Their metadata, permissions, creator (owner) and project membership live in the workspace ResourceInfo in ResourcesKBox; no workspace settings file is created. New workspaces record the authenticated creator. Existing workspaces without an owner remain administrable; membership in their access list does not imply ownership.

Submit a Workspace with SubmissionMode.REPLACE to replace metadata and/or privileges. A null metadata or privileges field leaves that part unchanged; empty metadata clears metadata and empty privileges restrict access to owner/admin (existing service grants are retained). Both settings are persisted in one catalog update. Submitted ownership and project membership are ignored. Only the stored owner or a service administrator may save, including through generic rights and resource-info update routes. Project locks and project edit permissions are irrelevant.

Workspace info exposes READ to authorized viewers and UPDATE/ADMINISTER to the owner/admin. Other viewers can audit settings without editing them. Reads honor explicit exclusions, while owner/admin access is retained even when not listed in the ACL. Catalog saves refresh cached workspace metadata and privileges without rebuilding project membership. The IDE settings tab uses this contract and retains drafts after failed saves.

Project settings saves preserve unrelated manifest fields. ProjectSettings.definedWorldview is an optional admin-only manifest update (null unchanged, blank removes it); all saves still require a project lock and edit access. Observer edits require a nonblank effective definedWorldview. Legacy META-INF/project.json metadata is migrated on save and that file is removed. Failure restores both original files. No permissions are migrated into the manifest.

Under Git, settings remain working-tree changes for the existing repository Save/Publish actions, which stage additions, modifications and deletions. The settings save leaves index and HEAD intact, rejects conflicts on either settings path and ignored manifest paths, and returns fresh repository state. Manifest and legacy settings paths are recognized during repository change processing so pull/discard operations refresh project settings and invalidate cached worldview metadata.

Project settings transport diagnostics

Project settings submissions carry workspace/project in the JSON body, but use the project identifier alone in the existing submit route's {urn} segment. Encoding the coordinate slash as %2F causes Tomcat to reject the request with HTTP 400 before controller authorization or observer validation. The project controller obtains the coordinates from the body.

ResourcesClient.submit uses strict PUT handling: HTTP/transport failures throw an error with the HTTP status instead of becoming an empty collection. The IDE shows service rejection text in the settings tab as well as forwarding notifications. A successful empty response is reported as a missing save result, without suggesting that notifications exist.

ProjectSettingsTransportTest covers the mounted URL, complete body coordinates, observer text, and HTTP 400/403/500 propagation. The encoded-slash rejection was reproduced against the local Resources service; this does not constitute an authenticated end-to-end settings save.