Skip to content

Folder-as-resource foundation: SKILL type, v2 route, whole-resource PUT/GET #1634

Description

@astsiapanay

Parent

#1633 — DIAL Folder As Resource (EPIC)

What to build

The foundational tracer bullet for folder-as-resource: a user can upload an agent skill as a folder and download it back as a ZIP, end-to-end.

  • Introduce a new, isolated SKILL resource type with URL group skills, stored under its own blob prefix. It must be invisible to the v1 files API — only the new v2 handler may read or write it.
  • Wire a new v2 route family: PUT /v2/skills/{bucket}/{path} and GET /v2/skills/{bucket}/{path} (whole resource, trailing slash). Use a reluctant path component. Reserve the path segments files and v/ as structural tokens (neither a folder nor a resource may be named files).
  • Introduce a generic engine (FolderResourceController + FolderResourceService) and the .dial-resource marker as a small self-describing JSON document holding type, schemaVersion, state, currentVersion, etag (aggregate), timestamps, author, and type-specific metadata.
  • Adopt the versioned-prefix layout: file content lives under v/{versionId}/...; the marker is a pointer to the current immutable version.
  • Introduce a per-type handler keyed by group. The first handler is SkillHandler: validate(files) requires SKILL.md at root with parseable YAML frontmatter (name + description required); buildMarkerMetadata(files) extracts name/description/version into the marker.
  • Whole-resource PUT: accept multipart/form-data, one part per file, each part's filename = relative path inside the resource. Write all parts under a fresh v/{versionId}/ prefix, run the handler, compute the aggregate etag, then commit with a single putResource of .dial-resource (guarded by If-Match). The marker is synthesized server-side (type derived from the URL group) so clients can never corrupt it.
  • Whole-resource GET: stream an application/zip produced on a worker thread (never buffer the whole archive); the .dial-resource marker is stripped from the archive.

Scope: root-level skills only (grouping folders, structural-invariant walks, single-file ops, delete, listing come in later slices). Reuse ResourceService, BlobStorage, EtagHeader/If-Match, LockService, and ResourceEvent propagation.

Acceptance criteria

  • A SKILL resource type exists with group skills and an isolated blob prefix; v1 files routes cannot read or write it.
  • PUT /v2/skills/{bucket}/{path}/ with a valid multipart body creates the skill: parts written under v/{versionId}/, .dial-resource marker committed in a single write, response returns 200 with the aggregate ETag.
  • PUT of a skill missing SKILL.md, or with unparseable/incomplete frontmatter (name/description), is rejected with 400 and writes nothing observable.
  • GET /v2/skills/{bucket}/{path}/ streams a ZIP of the current version's files with the marker stripped; round-tripping PUT→GET reproduces the uploaded file set.
  • ZIP generation runs off the event loop (worker thread); large files are streamed, not buffered whole.
  • If-Match on the whole-resource PUT is honored against the marker's aggregate etag.
  • Integration test covers the create→download round trip and the validation-rejection path.

Blocked by

None - can start immediately.

Metadata

Metadata

Assignees

Labels

ready-for-agentTriaged and ready for an agent to implement

Type

No type
No fields configured for issues without a type.

Projects

Status
No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions