feat(excititor): OCI OpenVEX artifact-backed configuration (SPRINT_20260423_001)
Closes SPRINT_20260423_001 — all 3 tasks DONE, sprint archived. Extends
the scalar persisted-settings model (7efa424fe) with an artifact-
reference storage layer for the complex OCI OpenVEX connector, which
carries image subscription lists + cosign keys + TUF roots that don't
fit the flat values/clearKeys shape.
OCI-CFG-001 — Storage model:
- New vex.provider_artifact_refs table via embedded migration
009_vex_provider_artifact_refs.sql (auto-applied per §2.7). Columns:
artifact_id (UUID PK), provider_id, tenant_id, mime_type, size_bytes,
sha256, payload bytea, staged_at, staged_by. RLS via
current_setting('app.tenant_id', TRUE); size_bytes CHECK >= 0.
- Upload / list / meta / delete API under /excititor/providers/{id}/
artifacts. Multipart upload returns { artifactId, sha256, ... };
meta never returns payload. Scopes: read=vex.read, write=vex.admin.
- VexProviderArtifactMaterializer: scoped session writes artifacts to
%TMP%/stella-excititor-artifacts/<guid>/<artifactId>.bin with
chmod 0700/0600 on POSIX; Dispose cleans up the scratch root.
IDisposable + IAsyncDisposable, idempotent cleanup.
- PostgresVexProviderArtifactStore uses raw Npgsql for bytea I/O
(bypasses EF compiled-model cache + change tracking); EF entity
ProviderArtifactRefRow added for future EF-consumer completeness.
OCI-CFG-002 — Config wiring:
- VexProviderConfigurationFieldDefinition.FieldShape extended:
scalar | list<string> | artifactRef | list<artifactRef>.
- list<string> encoded as newline-or-JSON text; artifactRef stored as
the artifact UUID. Snapshot exposes FieldShape for clients.
- OCI-specific blocked sub-codes (all inside PROVIDER_CONFIG_INVALID /
_REQUIRED envelope): PROVIDER_CONFIG_MISSING_IMAGE_SUBSCRIPTIONS,
_INVALID_IMAGE_REFERENCE, _MISSING_COSIGN_KEY, _MISSING_COSIGN_ISSUER,
_MISSING_COSIGN_SUBJECT, _MISSING_TUF_ROOT, _INVALID_COSIGN_MODE,
_HTTP_REGISTRY_BLOCKED.
- Readiness reuses OciOpenVexAttestationConnectorOptions validator.
OCI-CFG-003 — CLI + Web:
- CLI `stella vex providers configure` flags: --image (repeatable),
--upload-artifact <key>=@<path> (repeatable), --clear-artifact,
--list-artifacts, --host-path-compat (CLI-only compatibility mode).
- New subcommand `stella vex providers artifacts <provider>` lists
staged artifacts.
- OciOpenVexConfigurationComponent (Angular standalone): image-
subscription list editor, artifact slots with file upload + staged
meta, scalar fields for cosign mode/issuer/subject/registry auth/
offline bundle, staged-artifact table with delete. Conditionally
rendered in vex-provider-catalog when provider.id == 'excititor:
oci-openvex'.
- docs/modules/excititor/operations/provider-credentials.md §5 OCI
OpenVEX rewritten with 4 canonical setup flows: keyless, keypair,
TUF+offline, private-registry.
Tests: targeted xUnit via scripts/test-targeted-xunit.ps1:
- VexProviderConfigurationServiceTests — 8/8 (regression)
- VexProviderOciOpenVexTests — 13/13 (OCI schema + sub-codes + artifact
service size/quota/tenant-isolation + materializer tempfile lifecycle)
Worker-side wiring of VexProviderArtifactMaterializationSession into
DefaultVexProviderRunner (before cosign/TUF validation) is a natural
follow-up — the resolver + API + store exist; a future sprint ties
the runner into the session lifecycle when the first end-to-end OCI
provider scan lands.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>