@makaio/extension-artifact-patch 1.0.0-dev-1789118368073

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Makaio GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # @makaio/extension-artifact-patch
2
+
3
+ Portable patch-based Artifact revisions, applied through an authorized host.
4
+
5
+ A revision costs the size of the change instead of the size of the payload. The caller sends the
6
+ `artifact.patch` request — artifact identity, the `baseRevision` it was written against, and the
7
+ instructions — and the package applies them to a copy of the stored payload, validates the complete
8
+ result against the effective Kind schema, and asks the host to persist it.
9
+
10
+ ## Declared operators
11
+
12
+ `$set` and `$unset` address declared paths; `$push` and `$pull` address declared collections. This is
13
+ a subset of Mongo update semantics and it is closed: any other operator is rejected by the request
14
+ schema. One collection entry is addressed by field match (`tasks.$[entry].status` with
15
+ `arrayFilters: [{ 'entry.title': '…' }]`) or by position (`tasks.3.status`). Field match is what keeps
16
+ the addressing independent of the list order a caller read earlier.
17
+
18
+ Four deviations from Mongo are deliberate, because a write that quietly does nothing — or only part
19
+ of what was asked — is worse than a rejected one:
20
+
21
+ - **Addressing nothing is an error.** Mongo treats an unmatched `arrayFilters` update or `$pull` as a
22
+ no-op. Here it is `NO_MATCH`.
23
+ - **A path that only some selected entries carry is an error.** An instruction is all-or-nothing
24
+ across the entries a filter selects: if one of them does not carry an intermediate object or the
25
+ addressed position, applying the change to the rest would report a partial write as a complete
26
+ one. A missing intermediate is `PATH_NOT_RESOLVABLE`; a missing position is `NO_MATCH`.
27
+ - **An undeclared path is an error.** A misspelled field is never created; the Kind schema decides
28
+ which paths exist. A declared optional field that this revision does not carry stays valid.
29
+ - **`$unset` addresses object properties only.** A collection entry is removed with `$pull`, so an
30
+ element is never replaced by a hole.
31
+
32
+ `$push` follows Mongo in creating a declared collection that the revision does not yet carry, and
33
+ `$pull` follows Mongo in matching object elements by the fields its condition declares.
34
+
35
+ ## Concurrency and diagnostics
36
+
37
+ `baseRevision` is mandatory, and it is enforced twice. This package compares it against the revision
38
+ it loads, which rejects a stale caller cheaply and before any work; the host's `store` is the
39
+ authoritative check and must be a compare-and-swap against `previous.revision`, because two callers
40
+ can pass the first comparison concurrently. A `store` that reports a conflict is surfaced as
41
+ `BASE_REVISION_CONFLICT` exactly like the early rejection.
42
+
43
+ A conflict names the current revision and says what to do with it in the error's `repair` field.
44
+ Only an append at a fixed path can be resent as written: a `$push` whose every path segment is a
45
+ plain property adds an entry, and that means the same thing whatever else landed in between.
46
+ Everything else has to be rebased against a fresh read. `$set` replaces a value the caller has not
47
+ seen since; `$unset` and `$pull` delete state the caller has not re-read, which the concurrent
48
+ revision may have written for a reason; a position addresses a different entry once something is
49
+ inserted ahead of it; and a `$[filter]` placeholder addresses a set the concurrent revision may have
50
+ changed, by editing the compared field or by adding another matching entry, so even an append
51
+ through a filter can land somewhere the caller never addressed. Field match still keeps the
52
+ *addressing* independent of list order, which is why it exists — it just does not make a write safe
53
+ to repeat blindly.
54
+
55
+ A rejected write is a separate case from a refused one. A `store` that returns a conflict persisted
56
+ nothing. A `store` that throws leaves the outcome unknown — the contract covers the compare-and-swap,
57
+ not what a throw means — so the repair hint asks for a re-read instead of promising a safe retry.
58
+
59
+ Every rejection names the failing path and a repair hint; schema rejections add the expected type or
60
+ the allowed values per path. `dryRun` applies and validates without persisting.
61
+
62
+ ## Host boundary
63
+
64
+ An integrating product supplies an `ArtifactPatchHost` through `createArtifactPatchToolset(host)` or
65
+ `createArtifactPatchPackage(host)`. The host owns authorization, repository scope, effective Kind
66
+ discovery, and persistence. `store` receives the resolved previous revision — payload included — and
67
+ the request's optional `statusPath`, so a host layered over a lifecycle writer can derive the same
68
+ status observation a full revise produces. The package never issues raw Artifact bus requests and
69
+ never reaches a store directly, so a service handling `artifact.patch` and the `artifacts_patch` MCP
70
+ tool run the same engine over the same contract. Its default package marker contributes no tools until a host is
71
+ explicitly bound.
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "artifact-patch",
3
+ "displayName": "Artifact Patch Tools",
4
+ "version": "0.1.0",
5
+ "makaio": {
6
+ "framework": ">=0.1.0"
7
+ },
8
+ "surface": "headless",
9
+ "entrypoints": {
10
+ "server": true
11
+ },
12
+ "execution": "embedded"
13
+ }
@@ -0,0 +1,18 @@
1
+ import type { IMakaioBus } from '@makaio/framework/bus';
2
+ import type { MakaioNodeExtension } from '@makaio/framework/contracts/extension';
3
+ import type { ArtifactPatchHost } from './patch-artifact.js';
4
+ /**
5
+ * Create the patch-based Artifact revision extension, optionally bound to an authorized host.
6
+ * @param host - Access policy supplied by the hosting application.
7
+ * @returns An extension that contributes the patch tool only when a host is configured.
8
+ */
9
+ export declare function createArtifactPatchPackage(host?: ArtifactPatchHost): MakaioNodeExtension<IMakaioBus>;
10
+ /** Unbound package marker; hosts must explicitly contribute an authorized toolset. */
11
+ export declare const artifactPatchPackage: MakaioNodeExtension<IMakaioBus>;
12
+ export default artifactPatchPackage;
13
+ export { applyArtifactPatch } from './patch-engine.js';
14
+ export type { ArtifactPatchApplication, ArtifactPatchApplicationResult } from './patch-engine.js';
15
+ export { executePatchArtifact, patchArtifact } from './patch-artifact.js';
16
+ export type { ArtifactPatchHost, ArtifactPatchStoreConflict, ArtifactPatchStoreRequest, ArtifactPatchStoreResult, } from './patch-artifact.js';
17
+ export { createArtifactPatchToolset, createPatchArtifactTool } from './toolset.js';
18
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import{a as e,i as t,n,o as r,r as i,s as a,t as o}from"./src-D3gIWmhh.mjs";export{a as applyArtifactPatch,o as artifactPatchPackage,o as default,n as createArtifactPatchPackage,i as createArtifactPatchToolset,t as createPatchArtifactTool,e as executePatchArtifact,r as patchArtifact};
@@ -0,0 +1,103 @@
1
+ import { type ArtifactKindRegistration, type ArtifactPatchRequest, type ArtifactPatchResponse, type ArtifactRevision } from '@makaio/framework/contracts';
2
+ import { type ToolExecutionContext, type ToolResult } from '@makaio/framework/tools';
3
+ /** One new revision the host is asked to persist. */
4
+ export interface ArtifactPatchStoreRequest {
5
+ /**
6
+ * Exact revision the patch was applied to, payload included.
7
+ *
8
+ * The full revision travels rather than a reference so a host can derive the
9
+ * change it observes — a status transition, an audit entry — from the two
10
+ * payloads it has in hand, without a second read of a revision this package
11
+ * already resolved.
12
+ */
13
+ readonly previous: ArtifactRevision;
14
+ /** Patched payload, already validated against the effective kind schema. */
15
+ readonly data: Record<string, unknown>;
16
+ /**
17
+ * Caller-owned status observation for this write, as a `data`-relative JSON
18
+ * Pointer. Present exactly when the request named one; a host that emits
19
+ * status events reads the pointer in `previous.data` and in the payload it
20
+ * persists, exactly as a full revise does.
21
+ */
22
+ readonly statusPath?: string;
23
+ }
24
+ /** The artifact moved on before the write landed; nothing was persisted. */
25
+ export interface ArtifactPatchStoreConflict {
26
+ /** Revision the artifact carries instead of `previous.revision`. */
27
+ readonly conflictingRevision: string;
28
+ }
29
+ /** Either the persisted revision, or the conflict that stopped it. */
30
+ export type ArtifactPatchStoreResult = ArtifactRevision | ArtifactPatchStoreConflict;
31
+ /**
32
+ * Host-owned access boundary for patch-based Artifact revisions.
33
+ *
34
+ * The host owns authorization, repository scope, effective Kind discovery, and
35
+ * persistence. This package never issues raw Artifact bus requests and never
36
+ * reaches a store directly, so the same patch engine serves an MCP process and
37
+ * a service handling `artifact.patch` without either learning the other's
38
+ * transport.
39
+ */
40
+ export interface ArtifactPatchHost {
41
+ /** List effective registrations for a requested Kind. */
42
+ listKinds(kind: string, context: ToolExecutionContext): Promise<readonly ArtifactKindRegistration[]>;
43
+ /**
44
+ * Resolve the host-authorized current revision for one Kind and identity.
45
+ *
46
+ * The patch applies to this revision. Comparing its identifier with the
47
+ * caller's `baseRevision` rejects a stale caller before any work and names
48
+ * the current revision in the same step, so recovery never needs a re-read of
49
+ * the payload. It is not the authoritative concurrency check — `store` is,
50
+ * because two callers can pass this comparison at the same time.
51
+ */
52
+ resolveCurrent(ref: {
53
+ readonly kind: string;
54
+ readonly id: string;
55
+ }, context: ToolExecutionContext): Promise<ArtifactRevision | null>;
56
+ /**
57
+ * Persist the patched payload as the next revision, or refuse the write.
58
+ *
59
+ * This is the authoritative half of the optimistic concurrency check and it
60
+ * must be a compare-and-swap against `previous.revision`: the revision
61
+ * comparison this package makes before applying the patch is an early, cheap
62
+ * rejection, and two callers can pass it concurrently. A host that writes
63
+ * without comparing `previous.revision` reintroduces the lost update this
64
+ * contract exists to prevent.
65
+ *
66
+ * A thrown rejection is reported to the caller as an unknown outcome, because
67
+ * this contract cannot tell a write that never ran from one that committed
68
+ * before the failure surfaced. Refusing a write by returning
69
+ * `ArtifactPatchStoreConflict` is the only outcome that promises nothing was
70
+ * persisted.
71
+ */
72
+ store(request: ArtifactPatchStoreRequest, context: ToolExecutionContext): Promise<ArtifactPatchStoreResult>;
73
+ }
74
+ /**
75
+ * Revise one artifact by applying a patch to its current revision.
76
+ *
77
+ * The patch is applied to a copy of the stored payload, the complete result is
78
+ * validated against the effective kind schema, and only then is a new revision
79
+ * written. A dry run stops before the write and returns the same diagnostics,
80
+ * so a caller can prove a patch without producing history.
81
+ *
82
+ * This function is the whole operation behind the `artifact.patch` subject; a
83
+ * host serving that subject and the MCP facade run exactly the same code.
84
+ * @param input - Validated patch request.
85
+ * @param context - Tool execution context forwarded to the host.
86
+ * @param host - Host-owned access boundary.
87
+ * @returns The applied patch, or the rejection that stopped it.
88
+ */
89
+ export declare function patchArtifact(input: ArtifactPatchRequest, context: ToolExecutionContext, host: ArtifactPatchHost): Promise<ArtifactPatchResponse>;
90
+ /**
91
+ * Revise one artifact by patch through an explicit host-owned access boundary.
92
+ *
93
+ * Rejections that the caller can act on are returned in band as part of the
94
+ * contract's response, because the repair hint is the point. Only an absent
95
+ * host is a tool-level failure: without one there is nothing to authorize the
96
+ * write, so the request fails closed before any lookup.
97
+ * @param input - Validated patch request.
98
+ * @param context - Tool execution context supplied by the host.
99
+ * @param host - Optional authorized host boundary.
100
+ * @returns The patch outcome, or a whole-tool failure when no host is bound.
101
+ */
102
+ export declare function executePatchArtifact(input: ArtifactPatchRequest, context: ToolExecutionContext, host?: ArtifactPatchHost): Promise<ToolResult<ArtifactPatchResponse>>;
103
+ //# sourceMappingURL=patch-artifact.d.ts.map
@@ -0,0 +1,30 @@
1
+ import { type ArtifactPatchDocument, type ArtifactPatchError, type ArtifactPatchOperationResult } from '@makaio/framework/contracts';
2
+ /** A patched payload together with what each instruction changed. */
3
+ export interface ArtifactPatchApplication {
4
+ /** The patched payload, detached from the revision it was derived from. */
5
+ readonly data: Record<string, unknown>;
6
+ /** One entry per applied instruction, in application order. */
7
+ readonly operations: readonly ArtifactPatchOperationResult[];
8
+ }
9
+ /** Outcome of applying a patch document to one payload. */
10
+ export type ArtifactPatchApplicationResult = {
11
+ readonly ok: true;
12
+ readonly application: ArtifactPatchApplication;
13
+ } | {
14
+ readonly ok: false;
15
+ readonly error: ArtifactPatchError;
16
+ };
17
+ /**
18
+ * Apply a complete patch document to one artifact payload.
19
+ *
20
+ * The payload is copied first, so a rejected patch leaves the caller's value
21
+ * untouched and no partially applied result can escape. Instructions run in the
22
+ * order {@link artifactPatchInstructions} reports, so the same document always
23
+ * produces the same result.
24
+ * @param data - Payload of the revision the patch was written against.
25
+ * @param patch - Patch document already accepted by its schema.
26
+ * @param dataSchema - Serialized data schema of the effective kind.
27
+ * @returns The patched payload, or the first rejected instruction.
28
+ */
29
+ export declare function applyArtifactPatch(data: Record<string, unknown>, patch: ArtifactPatchDocument, dataSchema: Record<string, unknown>): ArtifactPatchApplicationResult;
30
+ //# sourceMappingURL=patch-engine.d.ts.map
@@ -0,0 +1,3 @@
1
+ export { default, applyArtifactPatch, artifactPatchPackage, createArtifactPatchPackage, createArtifactPatchToolset, createPatchArtifactTool, executePatchArtifact, patchArtifact, } from './index.js';
2
+ export type { ArtifactPatchApplication, ArtifactPatchApplicationResult, ArtifactPatchHost, ArtifactPatchStoreConflict, ArtifactPatchStoreRequest, ArtifactPatchStoreResult, } from './index.js';
3
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ import{a as e,i as t,n,o as r,r as i,s as a,t as o}from"./src-D3gIWmhh.mjs";export{a as applyArtifactPatch,o as artifactPatchPackage,o as default,n as createArtifactPatchPackage,i as createArtifactPatchToolset,t as createPatchArtifactTool,e as executePatchArtifact,r as patchArtifact};
@@ -0,0 +1 @@
1
+ import{ARTIFACT_COLLECTION_ELEMENT_SEGMENT as e,ArtifactPatchRequestSchema as t,ArtifactPatchResponseSchema as n,artifactPatchFilterName as r,artifactPatchInstructions as i,artifactPatchSegments as a,compileArtifactDataChecker as o,defineOwnValue as s,inspectArtifactDataLocation as c,isJsonObject as l,jsonEquals as u,ownValue as d,readArtifactTitle as f,readPropertyPath as p}from"@makaio/framework/contracts";import{ToolErrorCodes as m,defineTool as h,defineToolset as g,toolError as ee,toolSuccess as te,widenTool as _}from"@makaio/framework/tools";function v(t){return t.map(t=>t.kind===`property`?t.name:e)}function y(e){return e.length>0&&e.every(e=>typeof e!=`boolean`&&e.type===`array`)}function b(e){return Object.entries(e).map(([e,t])=>({path:e.split(`.`).slice(1),operand:t}))}function x(e){let t=new Map;for(let n of e.arrayFilters??[]){let e=r(n);e!==void 0&&t.set(e,b(n))}return t}function S(e,t){return t.every(({path:t,operand:n})=>u(p(e,t),n))}function C(e,t){return l(t)?l(e)?Object.entries(t).every(([t,n])=>u(d(e,t),n)):!1:u(e,t)}function w(e,t,n,r){let i=[];for(let a of e){if(t.kind===`property`){if(!l(a)||r&&!Object.hasOwn(a,t.name))continue;i.push({kind:`property`,container:a,key:t.name});continue}if(!Array.isArray(a))continue;if(t.kind===`index`){t.index<a.length&&i.push({kind:`element`,container:a,index:t.index});continue}let e=n.get(t.placeholder)??[];a.forEach((t,n)=>{S(t,e)&&i.push({kind:`element`,container:a,index:n})})}return i}function T(e){return e.kind===`property`?d(e.container,e.key):e.container[e.index]}function E(e,t){e.kind===`property`?s(e.container,e.key,t):e.container[e.index]=t}function ne(e,t,n){let r=[e];for(let e of t){let t=w(r,e,n,!0);if(t.length===0||e.kind!==`filter`&&t.length<r.length)return{ok:!1,segment:e};r=t.map(T)}return{ok:!0,containers:r}}function re(e){return e.kind===`property`?e.name:e.kind===`index`?String(e.index):`$[${e.placeholder}]`}function D(e,t,n,r,i){return{code:e,message:r,operator:t,path:n,repair:i}}function O(e,t,n){return D(`NO_MATCH`,e,t,n,`Addressing nothing is a failure, not a silent no-op: check the match values against the current revision.`)}function k(e,t){for(let n of e)E(n,structuredClone(t));return e.length}function A(e){for(let t of e)t.kind===`property`&&Reflect.deleteProperty(t.container,t.key);return e.length}function j(e,t){for(let n of e){let e=T(n);if(e===void 0){E(n,[structuredClone(t)]);continue}if(!Array.isArray(e))return;e.push(structuredClone(t))}return e.length}function M(e,t){let n=0;for(let r of e){let e=T(r);if(!Array.isArray(e))return;let i=e.filter(e=>!C(e,t));n+=e.length-i.length,e.splice(0,e.length,...i)}return n}function N(e,t,n){switch(e){case`$set`:return k(t,n);case`$unset`:return A(t);case`$push`:return j(t,n);case`$pull`:return M(t,n)}}function P(e,t,n){if(t.kind!==`property`){if(e===`$unset`)return D(`UNSUPPORTED_TARGET`,e,n,`$unset addresses object properties; '${n}' addresses a collection entry.`,`Remove a collection entry with $pull instead of $unset.`);if(e===`$push`||e===`$pull`)return D(`UNSUPPORTED_TARGET`,e,n,`${e} addresses a collection; '${n}' addresses one of its entries.`,`Point ${e} at the collection itself, without the trailing entry selector.`)}}function F(e,t,n,r){let i=c(e,v(r));if(i===void 0)return D(`PATH_NOT_DECLARED`,t,n,`The artifact kind does not declare '${n}'.`,`Correct the path to one the kind schema declares; a misspelled field is never created.`);if((t===`$push`||t===`$pull`)&&!y(i))return D(`TARGET_NOT_A_COLLECTION`,t,n,`The artifact kind declares '${n}' as something other than a collection.`,`Use $set to replace '${n}', or point ${t} at a declared collection.`)}function I(e,t,n,r){let{operator:i,path:o,value:s}=t,c=a(o),l=c.at(-1);if(l===void 0)return O(i,o,`'${o}' addresses nothing.`);let u=P(i,l,o);if(u)return u;let d=F(r,i,o,c);if(d)return d;let f=ne(e,c.slice(0,-1),n);if(!f.ok){let e=re(f.segment);return f.segment.kind===`property`?D(`PATH_NOT_RESOLVABLE`,i,o,`This revision has no value at '${e}' along '${o}'.`,`Set the value at '${e}' before addressing anything below it.`):O(i,o,`'${e}' in '${o}' addressed no entry in this revision.`)}let p=i===`$unset`||i===`$pull`,m=w(f.containers,l,n,p);if(m.length===0)return O(i,o,`'${o}' addressed no entry in this revision.`);if(l.kind===`index`&&m.length<f.containers.length)return O(i,o,`'${o}' addressed no entry in every selected collection of this revision.`);let h=N(i,m,s);return h===void 0?D(`TARGET_NOT_A_COLLECTION`,i,o,`The value at '${o}' is not a collection in this revision.`,`Use $set to replace '${o}', or point ${i} at a collection.`):h===0?O(i,o,`'${o}' changed nothing in this revision.`):{operator:i,path:o,matched:h}}function L(e,t,n){let r=structuredClone(e),a=x(t),o=[];for(let e of i(t)){let t=I(r,e,a,n);if(`code`in t)return{ok:!1,error:t};o.push(t)}return{ok:!0,application:{data:r,operations:o}}}function R(e){return`conflictingRevision`in e}function z(e){return e instanceof Error?e.message:String(e)}function B(e){return{ok:!1,error:e}}function V(e,t,n){return{refClass:`artifact`,kind:e,id:t,revision:n}}function H(e){return{path:e.path,reason:e.reason,...e.expectedType===void 0?{}:{expectedType:e.expectedType},...e.allowedValues===void 0?{}:{allowedValues:[...e.allowedValues]}}}function U(e){return i(e.patch).every(({operator:e,path:t})=>e===`$push`&&a(t).every(e=>e.kind===`property`))}function W(e,t){return{code:`BASE_REVISION_CONFLICT`,message:`Artifact '${e.ref.kind}:${e.ref.id}' has advanced to revision '${t}'.`,currentRevision:t,repair:U(e)?`Resend the same patch with baseRevision '${t}'; it only appends at a fixed path, with no position and no filter, so it does not depend on the payload you read.`:`Re-read the artifact at revision '${t}' and rewrite the patch: only an append at a fixed path survives a concurrent write, with no position and no filter. Replacing, removing, addressing an entry by position, and appending through a $[filter] placeholder all depend on the payload you read, which the concurrent revision may have changed.`}}function G(e){let t=e[0];if(!t)return`Correct the patched result so it satisfies the kind schema.`;let n=t.path===``?`the payload root`:`'${t.path}'`;return t.allowedValues?`${n} accepts one of: ${t.allowedValues.map(e=>typeof e==`string`?e:JSON.stringify(e)).join(`, `)}.`:t.expectedType?`${n} expects type ${t.expectedType}.`:`${n} ${t.reason}.`}function K(e,t){try{f(e,t.titlePath);return}catch(e){return{code:`SCHEMA_VALIDATION_FAILED`,message:`The patched result does not satisfy the '${t.kind}' data schema.`,issues:[{path:t.titlePath,reason:z(e)}],repair:`'${t.titlePath}' must be a nonblank string.`}}}function q(e,t){let n=e.filter(e=>e.kind===t.kind);return n.length===0?{code:`KIND_NOT_REGISTERED`,message:`Artifact kind '${t.kind}' is not registered.`,repair:`Register the kind, or address an artifact of a registered kind.`}:n.find(e=>e.schemaVersion===t.schemaVersion)||{code:`SCHEMA_VERSION_MISMATCH`,message:`Revision '${t.revision}' uses schema version ${t.schemaVersion}, for which '${t.kind}' has no registration.`,repair:`Register '${t.kind}' at schema version ${t.schemaVersion}, or migrate the artifact before patching it.`}}async function J(e,t,n){let r;try{r=await n.resolveCurrent({kind:e.ref.kind,id:e.ref.id},t)}catch(e){return{code:`HOST_FAILED`,message:`Artifact lookup failed: ${z(e)}`,repair:`Retry once the artifact store is reachable.`}}return r?r.kind!==e.ref.kind||r.id!==e.ref.id?{code:`HOST_FAILED`,message:`Artifact lookup returned a different artifact identity.`,repair:`Retry; the resolved artifact did not match the requested identity.`}:r.revision===e.baseRevision?r:W(e,r.revision):{code:`ARTIFACT_NOT_FOUND`,message:`Artifact '${e.ref.kind}:${e.ref.id}' was not found.`,repair:`Check the kind and identity, or create the artifact before revising it.`}}async function Y(e,t,n){let r=await J(e,t,n);if(`code`in r)return B(r);let i=V(r.kind,r.id,r.revision),a;try{a=await n.listKinds(e.ref.kind,t)}catch(e){return B({code:`HOST_FAILED`,message:`Artifact kind lookup failed: ${z(e)}`,repair:`Retry once the kind catalog is reachable.`})}let s=q(a,r);if(`code`in s)return B(s);let c=L(r.data,e.patch,s.dataSchema);if(!c.ok)return B(c.error);let l;try{l=o(s)(c.application.data)}catch(e){return B({code:`HOST_FAILED`,message:`Artifact kind '${s.kind}' could not compile its data schema: ${z(e)}`,repair:`Correct the registered data schema before patching artifacts of this kind.`})}if(!l.valid)return B({code:`SCHEMA_VALIDATION_FAILED`,message:`The patched result does not satisfy the '${s.kind}' data schema.`,issues:l.issues.map(H),repair:G(l.issues)});let u=K(c.application.data,s);if(u)return B(u);let d=[...c.application.operations];if(e.dryRun===!0)return{ok:!0,base:i,dryRun:!0,operations:d};let f;try{f=await n.store({previous:r,data:c.application.data,...e.statusPath===void 0?{}:{statusPath:e.statusPath}},t)}catch(e){return B({code:`HOST_FAILED`,message:`Artifact revision failed: ${z(e)}`,repair:`Re-read the artifact: the write may have been committed before the failure was reported. Retry only after confirming the change is absent.`})}return R(f)?B(W(e,f.conflictingRevision)):f.kind!==i.kind||f.id!==i.id||f.revision===i.revision?B({code:`HOST_FAILED`,message:`The store returned an artifact that is not a new revision of the patched one.`,repair:`Re-read the artifact before patching it again; the write outcome is unclear.`}):{ok:!0,base:i,dryRun:!1,artifact:V(f.kind,f.id,f.revision),operations:d}}async function X(e,t,n){return n?te(await Y(e,t,n)):ee(m.PERMISSION_DENIED,`Artifact revisions require an authorized host.`)}function Z(e){return h({name:`artifacts_patch`,description:`Revise an Artifact by sending only the change. Name the artifact, the baseRevision you read, and the instructions: $set and $unset on declared paths, $push and $pull on declared collections. Address one collection entry by field match with $[name] plus arrayFilters, or by position. An unknown path, an unknown operator and addressing nothing are all errors, never silent no-ops. Use dryRun to check a patch without writing. A stale baseRevision reports the current revision; follow the repair field of that error: only an append at a fixed path (no position, no filter) may be resent with the new baseRevision, anything else needs a fresh read and a rewritten patch.`,annotations:{readOnly:!1,idempotent:!1},inputSchema:t,outputSchema:n,execute:(t,n)=>X(t,n,e)})}function Q(e){return g({name:`artifact-patch`,description:`Revise Artifacts by patch through an authorized host.`,version:`0.1.0`,tools:[_(Z(e))]})}function $(e){return{name:`artifact-patch`,displayName:`Artifact Patch Tools`,version:`0.1.0`,surface:`headless`,tools:{createToolsets:()=>e?[Q(e)]:[]}}}const ie=$();export{X as a,Z as i,$ as n,Y as o,Q as r,L as s,ie as t};
@@ -0,0 +1,110 @@
1
+ import { type ArtifactPatchHost } from './patch-artifact.js';
2
+ /**
3
+ * Create an authorized patch-based Artifact revision tool.
4
+ *
5
+ * The tool is a facade and nothing more: its input and output are the
6
+ * `artifact.patch` request and response contracts unchanged, so an agent and a
7
+ * bus client describe the same write in the same words.
8
+ * @param host - Access policy supplied by the hosting application.
9
+ * @returns A patch tool bound to the supplied host.
10
+ */
11
+ export declare function createPatchArtifactTool(host: ArtifactPatchHost): import("@makaio/framework/tools").ToolDefinition<import("zod").ZodObject<{
12
+ ref: import("zod").ZodObject<{
13
+ kind: import("zod").ZodString;
14
+ id: import("zod").ZodString;
15
+ }, import("zod/v4/core").$strict>;
16
+ baseRevision: import("zod").ZodString;
17
+ patch: import("zod").ZodObject<{
18
+ $set: import("zod").ZodOptional<import("zod").ZodType<Record<string, import("@makaio/framework/contracts").JsonValue>, Record<string, import("@makaio/framework/contracts").JsonValue>, import("zod/v4/core").$ZodTypeInternals<Record<string, import("@makaio/framework/contracts").JsonValue>, Record<string, import("@makaio/framework/contracts").JsonValue>>>>;
19
+ $unset: import("zod").ZodOptional<import("zod").ZodType<Record<string, true>, Record<string, true>, import("zod/v4/core").$ZodTypeInternals<Record<string, true>, Record<string, true>>>>;
20
+ $push: import("zod").ZodOptional<import("zod").ZodType<Record<string, import("@makaio/framework/contracts").JsonValue>, Record<string, import("@makaio/framework/contracts").JsonValue>, import("zod/v4/core").$ZodTypeInternals<Record<string, import("@makaio/framework/contracts").JsonValue>, Record<string, import("@makaio/framework/contracts").JsonValue>>>>;
21
+ $pull: import("zod").ZodOptional<import("zod").ZodType<Record<string, import("@makaio/framework/contracts").JsonValue>, Record<string, import("@makaio/framework/contracts").JsonValue>, import("zod/v4/core").$ZodTypeInternals<Record<string, import("@makaio/framework/contracts").JsonValue>, Record<string, import("@makaio/framework/contracts").JsonValue>>>>;
22
+ arrayFilters: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodType<Record<string, import("@makaio/framework/contracts").JsonValue>, Record<string, import("@makaio/framework/contracts").JsonValue>, import("zod/v4/core").$ZodTypeInternals<Record<string, import("@makaio/framework/contracts").JsonValue>, Record<string, import("@makaio/framework/contracts").JsonValue>>>>>;
23
+ }, import("zod/v4/core").$strict>;
24
+ dryRun: import("zod").ZodOptional<import("zod").ZodBoolean>;
25
+ statusPath: import("zod").ZodOptional<import("zod").ZodString>;
26
+ }, import("zod/v4/core").$strict>, import("zod").ZodUnion<readonly [import("zod").ZodDiscriminatedUnion<[import("zod").ZodObject<{
27
+ ok: import("zod").ZodLiteral<true>;
28
+ base: import("zod").ZodObject<{
29
+ refClass: import("zod").ZodLiteral<"artifact">;
30
+ kind: import("zod").ZodString;
31
+ id: import("zod").ZodString;
32
+ revision: import("zod").ZodString;
33
+ }, import("zod/v4/core").$strict>;
34
+ operations: import("zod").ZodArray<import("zod").ZodObject<{
35
+ operator: import("zod").ZodEnum<{
36
+ $pull: "$pull";
37
+ $push: "$push";
38
+ $set: "$set";
39
+ $unset: "$unset";
40
+ }>;
41
+ path: import("zod").ZodString;
42
+ matched: import("zod").ZodNumber;
43
+ }, import("zod/v4/core").$strict>>;
44
+ dryRun: import("zod").ZodLiteral<true>;
45
+ }, import("zod/v4/core").$strict>, import("zod").ZodObject<{
46
+ ok: import("zod").ZodLiteral<true>;
47
+ base: import("zod").ZodObject<{
48
+ refClass: import("zod").ZodLiteral<"artifact">;
49
+ kind: import("zod").ZodString;
50
+ id: import("zod").ZodString;
51
+ revision: import("zod").ZodString;
52
+ }, import("zod/v4/core").$strict>;
53
+ operations: import("zod").ZodArray<import("zod").ZodObject<{
54
+ operator: import("zod").ZodEnum<{
55
+ $pull: "$pull";
56
+ $push: "$push";
57
+ $set: "$set";
58
+ $unset: "$unset";
59
+ }>;
60
+ path: import("zod").ZodString;
61
+ matched: import("zod").ZodNumber;
62
+ }, import("zod/v4/core").$strict>>;
63
+ dryRun: import("zod").ZodLiteral<false>;
64
+ artifact: import("zod").ZodObject<{
65
+ refClass: import("zod").ZodLiteral<"artifact">;
66
+ kind: import("zod").ZodString;
67
+ id: import("zod").ZodString;
68
+ revision: import("zod").ZodString;
69
+ }, import("zod/v4/core").$strict>;
70
+ }, import("zod/v4/core").$strict>], "dryRun">, import("zod").ZodObject<{
71
+ ok: import("zod").ZodLiteral<false>;
72
+ error: import("zod").ZodObject<{
73
+ code: import("zod").ZodEnum<{
74
+ ARTIFACT_NOT_FOUND: "ARTIFACT_NOT_FOUND";
75
+ BASE_REVISION_CONFLICT: "BASE_REVISION_CONFLICT";
76
+ HOST_FAILED: "HOST_FAILED";
77
+ KIND_NOT_REGISTERED: "KIND_NOT_REGISTERED";
78
+ NO_MATCH: "NO_MATCH";
79
+ PATH_NOT_DECLARED: "PATH_NOT_DECLARED";
80
+ PATH_NOT_RESOLVABLE: "PATH_NOT_RESOLVABLE";
81
+ SCHEMA_VALIDATION_FAILED: "SCHEMA_VALIDATION_FAILED";
82
+ SCHEMA_VERSION_MISMATCH: "SCHEMA_VERSION_MISMATCH";
83
+ TARGET_NOT_A_COLLECTION: "TARGET_NOT_A_COLLECTION";
84
+ UNSUPPORTED_TARGET: "UNSUPPORTED_TARGET";
85
+ }>;
86
+ message: import("zod").ZodString;
87
+ operator: import("zod").ZodOptional<import("zod").ZodEnum<{
88
+ $pull: "$pull";
89
+ $push: "$push";
90
+ $set: "$set";
91
+ $unset: "$unset";
92
+ }>>;
93
+ path: import("zod").ZodOptional<import("zod").ZodString>;
94
+ currentRevision: import("zod").ZodOptional<import("zod").ZodString>;
95
+ issues: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodObject<{
96
+ path: import("zod").ZodString;
97
+ reason: import("zod").ZodString;
98
+ expectedType: import("zod").ZodOptional<import("zod").ZodString>;
99
+ allowedValues: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodType<import("@makaio/framework/contracts").JsonValue, import("@makaio/framework/contracts").JsonValue, import("zod/v4/core").$ZodTypeInternals<import("@makaio/framework/contracts").JsonValue, import("@makaio/framework/contracts").JsonValue>>>>;
100
+ }, import("zod/v4/core").$strict>>>;
101
+ repair: import("zod").ZodString;
102
+ }, import("zod/v4/core").$strict>;
103
+ }, import("zod/v4/core").$strict>]>>;
104
+ /**
105
+ * Create a toolset for patch-based Artifact revisions through one authorized host.
106
+ * @param host - Access policy supplied by the hosting application.
107
+ * @returns A toolset containing the host-bound patch tool.
108
+ */
109
+ export declare function createArtifactPatchToolset(host: ArtifactPatchHost): import("@makaio/framework/tools").Toolset<Record<string, import("@makaio/framework/tools").AnyToolDefinition>>;
110
+ //# sourceMappingURL=toolset.d.ts.map
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@makaio/extension-artifact-patch",
3
+ "description": "Patch-based artifact revision tools for AI agents.",
4
+ "version": "1.0.0-dev-1789118368073",
5
+ "types": "dist/index.d.ts",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/makaio-ai/makaio-framework.git",
10
+ "directory": "extensions/artifact-patch"
11
+ },
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.mjs"
16
+ },
17
+ "./server": {
18
+ "types": "./dist/server.d.ts",
19
+ "default": "./dist/server.mjs"
20
+ },
21
+ "./package.json": "./package.json"
22
+ },
23
+ "peerDependencies": {
24
+ "@makaio/framework": ">=1.0.0-0 <2.0.0"
25
+ },
26
+ "dependencies": {
27
+ "zod": "4.4.3"
28
+ },
29
+ "scripts": {
30
+ "build": "bun build.ts",
31
+ "test": "vitest run"
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "descriptor.json",
36
+ "LICENSE",
37
+ "README.md"
38
+ ],
39
+ "license": "MIT",
40
+ "private": false,
41
+ "main": "dist/index.mjs",
42
+ "publishConfig": {
43
+ "access": "public"
44
+ }
45
+ }