consolidation of some of the modules, localization fixes, product advisories work, qa work

This commit is contained in:
master
2026-03-05 03:54:22 +02:00
parent 7bafcc3eef
commit 8e1cb9448d
3878 changed files with 72600 additions and 46861 deletions

View File

@@ -0,0 +1,12 @@
# StellaOps.PacksRegistry.WebService Agent Charter
## Mission
Deliver PacksRegistry HTTP API surface and composition root.
## Required Reading
- docs/modules/platform/architecture-overview.md
## Working Agreement
- Update sprint status in docs/implplan/SPRINT_*.md and local TASKS.md.
- Keep behavior deterministic (stable ordering, timestamps, hashes).
- Add or update WebService endpoint tests and auth/tenant coverage.

View File

@@ -0,0 +1,9 @@
using StellaOps.PacksRegistry.Core.Models;
namespace StellaOps.PacksRegistry.WebService.Contracts;
public sealed record AttestationResponse(string PackId, string Type, string Digest, DateTimeOffset CreatedAtUtc, string? Notes)
{
public static AttestationResponse From(AttestationRecord record) => new(record.PackId, record.Type, record.Digest, record.CreatedAtUtc, record.Notes);
}

View File

@@ -0,0 +1,9 @@
namespace StellaOps.PacksRegistry.WebService.Contracts;
public sealed class AttestationUploadRequest
{
public string? Type { get; set; }
public string? Content { get; set; }
public string? Notes { get; set; }
}

View File

@@ -0,0 +1,20 @@
using StellaOps.PacksRegistry.Core.Services;
namespace StellaOps.PacksRegistry.WebService.Contracts;
public sealed record ComplianceSummaryResponse(
int TotalPacks,
int UnsignedPacks,
int PromotedPacks,
int DeprecatedPacks,
int ParityReadyPacks,
int AttestedPacks)
{
public static ComplianceSummaryResponse From(ComplianceSummary summary) => new(
summary.TotalPacks,
summary.UnsignedPacks,
summary.PromotedPacks,
summary.DeprecatedPacks,
summary.ParityReadyPacks,
summary.AttestedPacks);
}

View File

@@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;
namespace StellaOps.PacksRegistry.WebService.Contracts;
public sealed record LifecycleRequest
{
[Required]
public string? State { get; init; }
public string? Notes { get; init; }
}

View File

@@ -0,0 +1,8 @@
using StellaOps.PacksRegistry.Core.Models;
namespace StellaOps.PacksRegistry.WebService.Contracts;
public sealed record LifecycleResponse(string PackId, string TenantId, string State, string? Notes, DateTimeOffset UpdatedAtUtc)
{
public static LifecycleResponse From(LifecycleRecord record) => new(record.PackId, record.TenantId, record.State, record.Notes, record.UpdatedAtUtc);
}

View File

@@ -0,0 +1,10 @@
namespace StellaOps.PacksRegistry.WebService.Contracts;
public sealed class MirrorRequest
{
public string? Id { get; set; }
public string? Upstream { get; set; }
public bool Enabled { get; set; } = true;
public string? Notes { get; set; }
}

View File

@@ -0,0 +1,9 @@
using StellaOps.PacksRegistry.Core.Models;
namespace StellaOps.PacksRegistry.WebService.Contracts;
public sealed record MirrorResponse(string Id, string TenantId, string Upstream, bool Enabled, string Status, DateTimeOffset UpdatedAtUtc, DateTimeOffset? LastSuccessfulSyncUtc, string? Notes)
{
public static MirrorResponse From(MirrorSourceRecord record) => new(record.Id, record.TenantId, record.UpstreamUri.ToString(), record.Enabled, record.Status, record.UpdatedAtUtc, record.LastSuccessfulSyncUtc, record.Notes);
}

View File

@@ -0,0 +1,8 @@
namespace StellaOps.PacksRegistry.WebService.Contracts;
public sealed class MirrorSyncRequest
{
public string? Status { get; set; }
public string? Notes { get; set; }
}

View File

@@ -0,0 +1,9 @@
namespace StellaOps.PacksRegistry.WebService.Contracts;
public sealed class OfflineSeedRequest
{
public string? TenantId { get; set; }
public bool IncludeContent { get; set; }
public bool IncludeProvenance { get; set; }
}

View File

@@ -0,0 +1,11 @@
namespace StellaOps.PacksRegistry.WebService.Contracts;
public sealed record PackManifestResponse(
string PackId,
string TenantId,
string Digest,
long ContentLength,
string? ProvenanceDigest,
long? ProvenanceLength,
DateTimeOffset CreatedAtUtc,
IReadOnlyDictionary<string, string>? Metadata);

View File

@@ -0,0 +1,18 @@
using StellaOps.PacksRegistry.Core.Models;
namespace StellaOps.PacksRegistry.WebService.Contracts;
public sealed record PackResponse(
string PackId,
string Name,
string Version,
string TenantId,
string Digest,
string? Signature,
string? ProvenanceUri,
DateTimeOffset CreatedAtUtc,
IReadOnlyDictionary<string, string>? Metadata)
{
public static PackResponse From(PackRecord record) =>
new(record.PackId, record.Name, record.Version, record.TenantId, record.Digest, record.Signature, record.ProvenanceUri, record.CreatedAtUtc, record.Metadata);
}

View File

@@ -0,0 +1,30 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace StellaOps.PacksRegistry.WebService.Contracts;
public sealed record PackUploadRequest
{
[Required]
public string? Name { get; init; }
[Required]
public string? Version { get; init; }
public string? TenantId { get; init; }
[Required]
public string? Content { get; init; } // base64 encoded
public string? Signature { get; init; }
public string? ProvenanceUri { get; init; }
/// <summary>
/// Optional provenance manifest content (base64). Stored and downloadable when present.
/// </summary>
public string? ProvenanceContent { get; init; }
[JsonPropertyName("metadata")]
public Dictionary<string, string>? Metadata { get; init; }
}

View File

@@ -0,0 +1,11 @@
using System.ComponentModel.DataAnnotations;
namespace StellaOps.PacksRegistry.WebService.Contracts;
public sealed record ParityRequest
{
[Required]
public string? Status { get; init; }
public string? Notes { get; init; }
}

View File

@@ -0,0 +1,8 @@
using StellaOps.PacksRegistry.Core.Models;
namespace StellaOps.PacksRegistry.WebService.Contracts;
public sealed record ParityResponse(string PackId, string TenantId, string Status, string? Notes, DateTimeOffset UpdatedAtUtc)
{
public static ParityResponse From(ParityRecord record) => new(record.PackId, record.TenantId, record.Status, record.Notes, record.UpdatedAtUtc);
}

View File

@@ -0,0 +1,12 @@
namespace StellaOps.PacksRegistry.WebService.Contracts;
public sealed class RotateSignatureRequest
{
public string? Signature { get; set; }
/// <summary>
/// Optional PEM-encoded public key to validate the new signature; falls back to configured verifier if not provided.
/// </summary>
public string? PublicKeyPem { get; set; }
}

View File

@@ -0,0 +1,50 @@
{
"openapi": "3.1.0",
"info": {
"title": "StellaOps Packs Registry",
"version": "0.1.0"
},
"paths": {
"/api/v1/packs/{packId}/manifest": {
"get": {
"summary": "Fetch pack manifest",
"parameters": [
{ "name": "packId", "in": "path", "required": true, "schema": { "type": "string" } },
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": false, "schema": { "type": "string" } }
],
"responses": {
"200": {
"description": "Manifest",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/PackManifest" }
}
}
},
"401": { "description": "Unauthorized" },
"403": { "description": "Forbidden" },
"404": { "description": "Not Found" }
}
}
}
},
"components": {
"schemas": {
"PackManifest": {
"type": "object",
"properties": {
"packId": { "type": "string" },
"tenantId": { "type": "string" },
"digest": { "type": "string" },
"contentLength": { "type": "integer", "format": "int64" },
"provenanceDigest": { "type": "string", "nullable": true },
"provenanceLength": { "type": "integer", "format": "int64", "nullable": true },
"createdAtUtc": { "type": "string", "format": "date-time" },
"metadata": { "type": "object", "additionalProperties": { "type": "string" }, "nullable": true }
},
"required": ["packId", "tenantId", "digest", "contentLength", "createdAtUtc"]
}
}
}
}

View File

@@ -0,0 +1,290 @@
{
"openapi": "3.1.0",
"info": {
"title": "StellaOps Packs Registry",
"version": "0.3.0"
},
"paths": {
"/api/v1/packs": {
"get": {
"summary": "List packs",
"parameters": [
{ "name": "tenant", "in": "query", "schema": { "type": "string" }, "description": "Filter to tenant; required when allowlists are configured." },
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": false, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "OK" }, "400": { "description": "Bad Request" }, "401": { "description": "Unauthorized" } }
},
"post": {
"summary": "Upload pack",
"requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/PackUpload" } } } },
"parameters": [
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": false, "schema": { "type": "string" } }
],
"responses": { "201": { "description": "Created" }, "400": { "description": "Bad Request" }, "401": { "description": "Unauthorized" } }
}
},
"/api/v1/packs/{packId}": {
"get": {
"summary": "Get pack",
"parameters": [
{ "name": "packId", "in": "path", "required": true, "schema": { "type": "string" } },
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": false, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "OK" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Not Found" } }
}
},
"/api/v1/packs/{packId}/content": {
"get": {
"summary": "Download pack content",
"parameters": [
{ "name": "packId", "in": "path", "required": true, "schema": { "type": "string" } },
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": false, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "Content" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Not Found" } }
}
},
"/api/v1/packs/{packId}/provenance": {
"get": {
"summary": "Download provenance",
"parameters": [
{ "name": "packId", "in": "path", "required": true, "schema": { "type": "string" } },
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": false, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "Provenance" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Not Found" } }
}
},
"/api/v1/packs/{packId}/manifest": {
"get": {
"summary": "Get manifest",
"parameters": [
{ "name": "packId", "in": "path", "required": true, "schema": { "type": "string" } },
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": false, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "Manifest" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Not Found" } }
}
},
"/api/v1/packs/{packId}/parity": {
"get": {
"summary": "Get parity status",
"parameters": [
{ "name": "packId", "in": "path", "required": true, "schema": { "type": "string" } },
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": false, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "Parity" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Not Found" } }
},
"post": {
"summary": "Set parity status",
"requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ParityRequest" } } } },
"parameters": [
{ "name": "packId", "in": "path", "required": true, "schema": { "type": "string" } },
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": true, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "Updated" }, "400": { "description": "Bad Request" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Not Found" } }
}
},
"/api/v1/packs/{packId}/lifecycle": {
"get": {
"summary": "Get lifecycle state",
"parameters": [
{ "name": "packId", "in": "path", "required": true, "schema": { "type": "string" } },
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": false, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "Lifecycle" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Not Found" } }
},
"post": {
"summary": "Set lifecycle state",
"requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/LifecycleRequest" } } } },
"parameters": [
{ "name": "packId", "in": "path", "required": true, "schema": { "type": "string" } },
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": true, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "Updated" }, "400": { "description": "Bad Request" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Not Found" } }
}
},
"/api/v1/packs/{packId}/signature": {
"post": {
"summary": "Rotate pack signature",
"requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RotateSignatureRequest" } } } },
"parameters": [
{ "name": "packId", "in": "path", "required": true, "schema": { "type": "string" } },
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": true, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "Rotated" }, "400": { "description": "Bad Request" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Not Found" } }
}
},
"/api/v1/packs/{packId}/attestations": {
"get": {
"summary": "List pack attestations",
"parameters": [
{ "name": "packId", "in": "path", "required": true, "schema": { "type": "string" } },
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": false, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "Attestations" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Not Found" } }
},
"post": {
"summary": "Upload attestation",
"requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AttestationUpload" } } } },
"parameters": [
{ "name": "packId", "in": "path", "required": true, "schema": { "type": "string" } },
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": true, "schema": { "type": "string" } }
],
"responses": { "201": { "description": "Created" }, "400": { "description": "Bad Request" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Not Found" } }
}
},
"/api/v1/packs/{packId}/attestations/{type}": {
"get": {
"summary": "Download attestation",
"parameters": [
{ "name": "packId", "in": "path", "required": true, "schema": { "type": "string" } },
{ "name": "type", "in": "path", "required": true, "schema": { "type": "string" } },
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": false, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "Attestation" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Not Found" } }
}
},
"/api/v1/export/offline-seed": {
"post": {
"summary": "Export offline seed archive",
"requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OfflineSeedRequest" } } } },
"parameters": [
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": false, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "Archive" }, "400": { "description": "Bad Request" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" } }
}
},
"/api/v1/mirrors": {
"get": {
"summary": "List mirror sources",
"parameters": [
{ "name": "tenant", "in": "query", "schema": { "type": "string" } },
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": false, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "OK" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" } }
},
"post": {
"summary": "Register or update mirror source",
"requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MirrorRequest" } } } },
"parameters": [
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": true, "schema": { "type": "string" } }
],
"responses": { "201": { "description": "Created" }, "400": { "description": "Bad Request" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" } }
}
},
"/api/v1/mirrors/{id}/sync": {
"post": {
"summary": "Mark mirror sync status",
"requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/MirrorSyncRequest" } } } },
"parameters": [
{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": true, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "Updated" }, "400": { "description": "Bad Request" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" }, "404": { "description": "Not Found" } }
}
},
"/api/v1/compliance/summary": {
"get": {
"summary": "Compliance summary",
"parameters": [
{ "name": "tenant", "in": "query", "schema": { "type": "string" } },
{ "name": "X-API-Key", "in": "header", "required": false, "schema": { "type": "string" } },
{ "name": "X-StellaOps-Tenant", "in": "header", "required": false, "schema": { "type": "string" } }
],
"responses": { "200": { "description": "Summary" }, "401": { "description": "Unauthorized" }, "403": { "description": "Forbidden" } }
}
}
},
"components": {
"schemas": {
"PackUpload": {
"type": "object",
"properties": {
"name": { "type": "string" },
"version": { "type": "string" },
"tenantId": { "type": "string" },
"content": { "type": "string", "format": "byte" },
"signature": { "type": "string" },
"provenanceUri": { "type": "string" },
"provenanceContent": { "type": "string", "format": "byte" },
"metadata": { "type": "object", "additionalProperties": { "type": "string" } }
},
"required": ["name", "version", "content"]
},
"ParityRequest": {
"type": "object",
"properties": {
"status": { "type": "string" },
"notes": { "type": "string" }
},
"required": ["status"]
},
"LifecycleRequest": {
"type": "object",
"properties": {
"state": { "type": "string", "enum": ["promoted", "deprecated", "draft"] },
"notes": { "type": "string" }
},
"required": ["state"]
},
"RotateSignatureRequest": {
"type": "object",
"properties": {
"signature": { "type": "string" },
"publicKeyPem": { "type": "string" }
},
"required": ["signature"]
},
"OfflineSeedRequest": {
"type": "object",
"properties": {
"tenantId": { "type": "string" },
"includeContent": { "type": "boolean" },
"includeProvenance": { "type": "boolean" }
}
},
"AttestationUpload": {
"type": "object",
"properties": {
"type": { "type": "string" },
"content": { "type": "string", "format": "byte" },
"notes": { "type": "string" }
},
"required": ["type", "content"]
},
"MirrorRequest": {
"type": "object",
"properties": {
"id": { "type": "string" },
"upstream": { "type": "string", "format": "uri" },
"enabled": { "type": "boolean" },
"notes": { "type": "string" }
},
"required": ["id", "upstream"]
},
"MirrorSyncRequest": {
"type": "object",
"properties": {
"status": { "type": "string" },
"notes": { "type": "string" }
}
}
}
}
}

View File

@@ -0,0 +1,8 @@
namespace StellaOps.PacksRegistry.WebService.Options;
public sealed class AuthOptions
{
public string? ApiKey { get; set; }
public string[] AllowedTenants { get; set; } = Array.Empty<string>();
}

View File

@@ -0,0 +1,6 @@
namespace StellaOps.PacksRegistry.WebService.Options;
public sealed class VerificationOptions
{
public string? PublicKeyPem { get; set; }
}

View File

@@ -0,0 +1,14 @@
{
"profiles": {
"StellaOps.PacksRegistry.WebService": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"STELLAOPS_WEBSERVICES_CORS": "true",
"STELLAOPS_WEBSERVICES_CORS_ORIGIN": "https://stella-ops.local,https://stella-ops.local:10000,https://localhost:10000"
},
"applicationUrl": "https://localhost:10340;http://localhost:10341"
}
}
}

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" ?>
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="StellaOps.PacksRegistry.Tests" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\StellaOps.PacksRegistry.Core\StellaOps.PacksRegistry.Core.csproj"/>
<ProjectReference Include="..\StellaOps.PacksRegistry.Infrastructure\StellaOps.PacksRegistry.Infrastructure.csproj"/>
<ProjectReference Include="..\..\StellaOps.PacksRegistry.__Libraries\StellaOps.PacksRegistry.Persistence\StellaOps.PacksRegistry.Persistence.csproj"/>
<ProjectReference Include="..\..\..\Router/__Libraries/StellaOps.Router.AspNet\StellaOps.Router.AspNet.csproj"/>
<ProjectReference Include="..\..\..\Authority\StellaOps.Authority\StellaOps.Auth.ServerIntegration\StellaOps.Auth.ServerIntegration.csproj"/>
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Localization\StellaOps.Localization.csproj"/>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Translations\*.json" />
</ItemGroup>
<PropertyGroup Label="StellaOpsReleaseVersion">
<Version>1.0.0-alpha1</Version>
<InformationalVersion>1.0.0-alpha1</InformationalVersion>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,6 @@
@StellaOps.PacksRegistry.WebService_HostAddress = http://localhost:5151
GET {{StellaOps.PacksRegistry.WebService_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@@ -0,0 +1,12 @@
# StellaOps.PacksRegistry.WebService Task Board
This board mirrors active sprint tasks for this module.
Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229_049_BE_csproj_audit_maint_tests.md`.
| Task ID | Status | Notes |
| --- | --- | --- |
| AUDIT-0433-M | DONE | Revalidated 2026-01-07; maintainability audit for StellaOps.PacksRegistry.WebService. |
| AUDIT-0433-T | DONE | Revalidated 2026-01-07; test coverage audit for StellaOps.PacksRegistry.WebService. |
| AUDIT-0433-A | TODO | Revalidated 2026-01-07 (open findings). |
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
| SPRINT-312-003 | DONE | Postgres-first storage driver migration with seed-fs payload contract wired in Program startup (pack/provenance/attestation payload channel). |

View File

@@ -0,0 +1,35 @@
{
"_meta": { "locale": "en-US", "namespace": "packsregistry", "version": "1.0" },
"packsregistry.packs.upload_description": "Uploads a new policy pack as base64-encoded content with optional signature and provenance attachment. Returns 201 Created with the registered pack record and assigned pack ID. Requires the X-StellaOps-Tenant header or a tenantId body field.",
"packsregistry.packs.list_description": "Returns the list of policy packs for the specified tenant, optionally excluding deprecated packs. When tenant allowlists are configured, a tenant query parameter or X-StellaOps-Tenant header is required.",
"packsregistry.packs.get_description": "Returns the metadata record for the specified pack ID including tenant, digest, provenance URI, and creation timestamp. Returns 403 if the caller's tenant allowlist does not include the pack's tenant. Returns 404 if the pack ID is not found.",
"packsregistry.packs.get_content_description": "Downloads the binary content of the specified pack as an octet-stream. The response includes an X-Content-Digest header with the stored digest for integrity verification. Returns 403 if the tenant does not match. Returns 404 if the pack or its content is not found.",
"packsregistry.packs.get_provenance_description": "Downloads the provenance document attached to the specified pack as a JSON file. The response includes an X-Provenance-Digest header when a digest is stored. Returns 404 if the pack or its provenance attachment is not found.",
"packsregistry.packs.get_manifest_description": "Returns a structured manifest for the specified pack including pack ID, tenant, content digest and size, provenance digest and size, creation timestamp, and attached metadata. Returns 403 if the tenant does not match. Returns 404 if the pack is not found.",
"packsregistry.packs.rotate_signature_description": "Replaces the stored signature on a pack with a new signature, optionally using a caller-supplied public key PEM for verification instead of the server default. Returns the updated pack record on success. Returns 400 if the new signature is invalid or rotation fails.",
"packsregistry.attestations.upload_description": "Attaches a typed attestation document to a pack as base64-encoded content. The type field identifies the attestation kind (e.g., sbom, scan-result). Returns 201 Created with the stored attestation record. Returns 400 if type or content is missing or the content is not valid base64.",
"packsregistry.attestations.list_description": "Returns all attestation records stored for the specified pack. Returns 404 if no attestations exist for the pack. Returns 403 if the X-StellaOps-Tenant header does not match the tenant of the stored attestations.",
"packsregistry.attestations.get_content_description": "Downloads the binary content of a specific attestation type for the specified pack. The response includes an X-Attestation-Digest header for integrity verification. Returns 403 if the tenant does not match. Returns 404 if the pack or the named attestation type is not found.",
"packsregistry.parity.get_description": "Returns the parity status record for the specified pack, indicating whether the pack content is consistent across mirror sites. Returns 403 if the tenant does not match. Returns 404 if no parity record exists for the pack.",
"packsregistry.lifecycle.get_description": "Returns the current lifecycle state record for the specified pack including state name, transition timestamp, and any associated notes. Returns 403 if the tenant does not match. Returns 404 if no lifecycle record exists for the pack.",
"packsregistry.lifecycle.set_description": "Transitions the specified pack to a new lifecycle state (e.g., active, deprecated, archived) with optional notes. Returns the updated lifecycle record. Returns 400 if the state value is missing or the transition is invalid.",
"packsregistry.parity.set_description": "Records the parity check result for the specified pack, marking it as verified, mismatch, or unknown with optional notes. Returns the updated parity record. Returns 400 if the status value is missing or the parity update fails.",
"packsregistry.export.offline_seed_description": "Generates a ZIP archive containing all packs for the specified tenant, optionally including binary content and provenance documents, suitable for seeding an air-gapped PacksRegistry instance. When tenant allowlists are configured, a tenant ID is required.",
"packsregistry.mirrors.upsert_description": "Creates or updates a mirror registration for the specified tenant, associating a mirror ID with an upstream URL and enabled state. Returns 201 Created with the stored mirror record. Returns 400 if required fields are missing.",
"packsregistry.mirrors.list_description": "Returns all mirror registrations for the specified tenant, or all mirrors if no tenant filter is applied. Returns 403 if the caller's tenant allowlist excludes the requested tenant.",
"packsregistry.mirrors.mark_sync_description": "Records the outcome of a mirror synchronization attempt for the specified mirror ID, updating its sync status and optional notes. Returns the updated mirror record. Returns 404 if the mirror ID is not found.",
"packsregistry.compliance.summary_description": "Returns a compliance summary for the specified tenant's pack collection including signed pack count, unsigned count, packs with attestations, deprecated packs, and mirror sync status breakdown. Returns 403 if the tenant is not allowed.",
"packsregistry.error.tenant_missing_header_or_body": "X-StellaOps-Tenant header or tenantId is required.",
"packsregistry.error.content_missing": "Content (base64) is required.",
"packsregistry.error.content_base64_invalid": "Content must be valid base64.",
"packsregistry.error.tenant_missing_query_or_header": "tenant query parameter or X-StellaOps-Tenant header is required when tenant allowlists are configured.",
"packsregistry.error.tenant_missing_header": "X-StellaOps-Tenant header is required.",
"packsregistry.error.tenant_missing_body_or_header": "tenantId or X-StellaOps-Tenant header is required when tenant allowlists are configured.",
"packsregistry.error.signature_missing": "signature is required.",
"packsregistry.error.attestation_missing": "type and content are required.",
"packsregistry.error.state_missing": "state is required.",
"packsregistry.error.status_missing": "status is required.",
"packsregistry.error.mirror_missing": "id and upstream are required."
}

View File

@@ -0,0 +1 @@
{"packId":"demo-pack@1.0.0","tenantId":"tenant-a","type":"dsse","digest":"sha256:934cd2191ed43ee19cd0a30506fa813f58ac41ff78df5e3154eda5c507b570f1","createdAtUtc":"2026-02-11T07:57:29.9780688+00:00","notes":"qa attestation"}

View File

@@ -0,0 +1,6 @@
{"packId":"demo-pack@1.0.0","tenantId":"tenant-a","event":"pack.uploaded","occurredAtUtc":"2026-02-11T07:57:29.8146776+00:00","actor":null,"notes":"https://example/prov.json"}
{"packId":null,"tenantId":"tenant-a","event":"mirror.upserted","occurredAtUtc":"2026-02-11T07:57:29.9030336+00:00","actor":null,"notes":"https://mirror.example.local/repo"}
{"packId":null,"tenantId":"tenant-a","event":"mirror.sync","occurredAtUtc":"2026-02-11T07:57:29.9430852+00:00","actor":null,"notes":"synced"}
{"packId":"demo-pack@1.0.0","tenantId":"tenant-a","event":"attestation.uploaded","occurredAtUtc":"2026-02-11T07:57:29.9780688+00:00","actor":null,"notes":"qa attestation"}
{"packId":"demo-pack@1.0.0","tenantId":"tenant-a","event":"lifecycle.updated","occurredAtUtc":"2026-02-11T07:57:30.0554898+00:00","actor":null,"notes":"qa deprecate"}
{"packId":null,"tenantId":"tenant-a","event":"offline.seed.exported","occurredAtUtc":"2026-02-11T07:57:30.1450257+00:00","actor":null,"notes":"with-content"}

View File

@@ -0,0 +1 @@
{"packId":"demo-pack@1.0.0","name":"demo-pack","version":"1.0.0","tenantId":"tenant-a","digest":"sha256:521aae173000ef3fdf35987542609b641ccae1882bfe06e616569b8e2e877e3e","signature":null,"provenanceUri":"https://example/prov.json","provenanceDigest":"sha256:60191507d86c569c572ba250b28ce4b97eb70ab7c50225ffbf0ab82aea66c85a","createdAtUtc":"2026-02-11T07:57:29.8146776+00:00","metadata":null}

View File

@@ -0,0 +1 @@
{"packId":"demo-pack@1.0.0","tenantId":"tenant-a","state":"deprecated","notes":"qa deprecate","updatedAtUtc":"2026-02-11T07:57:30.0554898+00:00"}

View File

@@ -0,0 +1,2 @@
{"id":"mirror-a","tenantId":"tenant-a","upstreamUri":"https://mirror.example.local/repo","enabled":true,"status":"enabled","updatedAtUtc":"2026-02-11T07:57:29.9030336+00:00","notes":"initial mirror","lastSuccessfulSyncUtc":null}
{"id":"mirror-a","tenantId":"tenant-a","upstreamUri":"https://mirror.example.local/repo","enabled":true,"status":"synced","updatedAtUtc":"2026-02-11T07:57:29.9430852+00:00","notes":"sync ok","lastSuccessfulSyncUtc":"2026-02-11T07:57:29.9431112+00:00"}

View File

@@ -0,0 +1 @@
{"packId":"demo-pack@1.0.0","tenantId":"tenant-a","type":"dsse","digest":"sha256:934cd2191ed43ee19cd0a30506fa813f58ac41ff78df5e3154eda5c507b570f1","createdAtUtc":"2026-02-11T08:03:51.8803767+00:00","notes":"qa attestation"}

View File

@@ -0,0 +1,7 @@
{"packId":"demo-pack@1.0.0","tenantId":"tenant-a","event":"pack.uploaded","occurredAtUtc":"2026-02-11T08:03:51.6949543+00:00","actor":null,"notes":"https://example/prov.json"}
{"packId":null,"tenantId":"tenant-a","event":"mirror.upserted","occurredAtUtc":"2026-02-11T08:03:51.7993693+00:00","actor":null,"notes":"https://mirror.example.local/repo"}
{"packId":null,"tenantId":"tenant-a","event":"mirror.sync","occurredAtUtc":"2026-02-11T08:03:51.8461881+00:00","actor":null,"notes":"synced"}
{"packId":"demo-pack@1.0.0","tenantId":"tenant-a","event":"attestation.uploaded","occurredAtUtc":"2026-02-11T08:03:51.8803767+00:00","actor":null,"notes":"qa attestation"}
{"packId":"demo-pack@1.0.0","tenantId":"tenant-a","event":"lifecycle.updated","occurredAtUtc":"2026-02-11T08:03:51.968221+00:00","actor":null,"notes":"qa deprecate"}
{"packId":null,"tenantId":"tenant-a","event":"offline.seed.exported","occurredAtUtc":"2026-02-11T08:03:52.0633922+00:00","actor":null,"notes":"with-content"}
{"packId":"demo-pack@1.0.0","tenantId":"tenant-a","event":"parity.updated","occurredAtUtc":"2026-02-11T08:05:50.9258486+00:00","actor":null,"notes":"primary updated"}

View File

@@ -0,0 +1 @@
{"packId":"demo-pack@1.0.0","name":"demo-pack","version":"1.0.0","tenantId":"tenant-a","digest":"sha256:521aae173000ef3fdf35987542609b641ccae1882bfe06e616569b8e2e877e3e","signature":null,"provenanceUri":"https://example/prov.json","provenanceDigest":"sha256:60191507d86c569c572ba250b28ce4b97eb70ab7c50225ffbf0ab82aea66c85a","createdAtUtc":"2026-02-11T08:03:51.6949543+00:00","metadata":null}

View File

@@ -0,0 +1 @@
{"packId":"demo-pack@1.0.0","tenantId":"tenant-a","state":"deprecated","notes":"qa deprecate","updatedAtUtc":"2026-02-11T08:03:51.968221+00:00"}

View File

@@ -0,0 +1,2 @@
{"id":"mirror-a","tenantId":"tenant-a","upstreamUri":"https://mirror.example.local/repo","enabled":true,"status":"enabled","updatedAtUtc":"2026-02-11T08:03:51.7993693+00:00","notes":"initial mirror","lastSuccessfulSyncUtc":null}
{"id":"mirror-a","tenantId":"tenant-a","upstreamUri":"https://mirror.example.local/repo","enabled":true,"status":"synced","updatedAtUtc":"2026-02-11T08:03:51.8461881+00:00","notes":"sync ok","lastSuccessfulSyncUtc":"2026-02-11T08:03:51.8462195+00:00"}

View File

@@ -0,0 +1 @@
{"packId":"demo-pack@1.0.0","tenantId":"tenant-a","status":"out_of_sync","notes":"primary updated","updatedAtUtc":"2026-02-11T08:05:50.9258486+00:00"}