The artifacts/ directory matched a repo-wide ignore rule and was left out of the UI card commit; its suites only passed locally because the files existed on disk. Force-track the complete card so the source push carries every module the tests import. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BvMSXH8VH2tMWXanb8SJdf
95 lines
4.1 KiB
TypeScript
95 lines
4.1 KiB
TypeScript
/** Same-origin endpoint descriptions for a future HUX-04 backend adapter. */
|
|
|
|
import { artifactsEnabled } from "./model.ts";
|
|
import { isOpaqueId, normalizeIdentity, scopedPath } from "./security.ts";
|
|
import type {
|
|
ArtifactEndpoint,
|
|
ArtifactEndpointContract,
|
|
ArtifactScope,
|
|
HuxIdentity,
|
|
} from "./types.ts";
|
|
|
|
interface FoundationClient {
|
|
apiVersion: string;
|
|
identity: HuxIdentity;
|
|
endpoint(path: string): string;
|
|
}
|
|
|
|
function endpoint(method: ArtifactEndpoint["method"], path: string, responseSchema: string,
|
|
requiredBody?: readonly string[]): ArtifactEndpoint {
|
|
return Object.freeze({method, path, responseSchema, ...(requiredBody ? {requiredBody} : {})});
|
|
}
|
|
|
|
function versionNumber(value: number): string {
|
|
if (!Number.isSafeInteger(value) || value < 1) throw new TypeError("Artifact version is invalid");
|
|
return String(value);
|
|
}
|
|
|
|
/**
|
|
* The server derives identity from the authenticated session and verifies the
|
|
* project/conversation path binding. Mutation bodies MUST carry
|
|
* expected_current_version and return 409 on stale state. Existing versions
|
|
* are never PATCHed or deleted; edits append a new content_ref-backed version.
|
|
*/
|
|
export function createArtifactEndpointContract(client: FoundationClient, scope: ArtifactScope,
|
|
flags?: Iterable<string>): ArtifactEndpointContract | null {
|
|
if (!artifactsEnabled(flags)) return null;
|
|
if (client.apiVersion !== "hux.v1") throw new TypeError("HUX v1 client required");
|
|
normalizeIdentity({tenant_ref: client.identity.tenantRef, user_ref: client.identity.userRef,
|
|
surface: client.identity.surface});
|
|
const collection = scopedPath("/projects", scope);
|
|
|
|
function itemPath(artifactId: string): string {
|
|
if (!isOpaqueId(artifactId, "art")) throw new TypeError("Opaque artifact id required");
|
|
return scopedPath("/projects", scope, artifactId);
|
|
}
|
|
|
|
function itemEndpoint(artifactId: string, suffix = ""): string {
|
|
return client.endpoint(`${itemPath(artifactId)}${suffix}`);
|
|
}
|
|
|
|
return Object.freeze({
|
|
list: endpoint("GET", client.endpoint(collection), "hux.artifact_workspace.v1"),
|
|
item(artifactId) {
|
|
return endpoint("GET", itemEndpoint(artifactId), "hux.artifact_response.v1");
|
|
},
|
|
preview(artifactId, version) {
|
|
return endpoint("GET", itemEndpoint(artifactId, `/versions/${versionNumber(version)}/preview`),
|
|
"hux.artifact_preview.v1");
|
|
},
|
|
diff(artifactId, fromVersion, toVersion) {
|
|
if (fromVersion >= toVersion) throw new TypeError("Artifact diff range is invalid");
|
|
return endpoint("POST", itemEndpoint(artifactId, "/diffs"), "hux.artifact_diff.v1",
|
|
Object.freeze(["from_version", "to_version", "expected_current_version"]));
|
|
},
|
|
appendVersion(artifactId) {
|
|
return endpoint("POST", itemEndpoint(artifactId, "/versions"), "hux.artifact_response.v1",
|
|
Object.freeze(["content_ref", "expected_current_version"]));
|
|
},
|
|
continueEdit(artifactId) {
|
|
return endpoint("POST", itemEndpoint(artifactId, "/continue"), "hux.artifact_edit_intent.v1",
|
|
Object.freeze(["version", "expected_current_version"]));
|
|
},
|
|
authorize(artifactId, action) {
|
|
if (action !== "download" && action !== "share") throw new TypeError("Artifact action is invalid");
|
|
return endpoint("POST", itemEndpoint(artifactId, `/authorizations/${action}`),
|
|
"hux.artifact_authorization.v1", Object.freeze(["expected_current_version"]));
|
|
},
|
|
download(artifactId, version) {
|
|
return endpoint("GET", itemEndpoint(artifactId, `/versions/${versionNumber(version)}/download`),
|
|
"hux.artifact_content.v1");
|
|
},
|
|
share(artifactId, version) {
|
|
return endpoint("POST", itemEndpoint(artifactId, `/versions/${versionNumber(version)}/shares`),
|
|
"hux.artifact_share.v1", Object.freeze(["expected_current_version"]));
|
|
},
|
|
promote(artifactId) {
|
|
return endpoint("POST", itemEndpoint(artifactId, "/promotions"), "hux.artifact_response.v1",
|
|
Object.freeze(["target_project_id", "version", "expected_current_version"]));
|
|
},
|
|
sources(artifactId) {
|
|
return endpoint("GET", itemEndpoint(artifactId, "/sources"), "hux.artifact_sources.v1");
|
|
},
|
|
});
|
|
}
|